From 15430c73ab735a058a5c67a13d9c59c2a7d81a83 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Fri, 13 Dec 2024 16:06:56 +0530 Subject: [PATCH 01/42] feat(web): use-atlas-spam-api --- web/src/hooks/useSpamEvidence.ts | 44 +++++++++++++++++++ .../Cases/CaseDetails/Evidence/index.tsx | 30 ++++++++++--- web/tsconfig.json | 3 +- 3 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 web/src/hooks/useSpamEvidence.ts diff --git a/web/src/hooks/useSpamEvidence.ts b/web/src/hooks/useSpamEvidence.ts new file mode 100644 index 000000000..93f80d6b3 --- /dev/null +++ b/web/src/hooks/useSpamEvidence.ts @@ -0,0 +1,44 @@ +import { useQuery } from "@tanstack/react-query"; +import { request } from "graphql-request"; + +import { isKlerosNeo, isKlerosUniversity, isTestnetDeployment } from "src/consts"; +import { graphql } from "src/graphql"; +import { isUndefined } from "src/utils"; + +const spamEvidenceQuery = graphql(` + query SpamEvidences($deployment: CourtV2Deployment!) { + courtv2EvidenceSpamsByDeployment(deployment: $deployment) { + disputeEvidenceIndex + dispute + } + } +`); + +type SpamEvidences = { + courtv2EvidenceSpamsByDeployment: { disputeEvidenceIndex: string; dispute: string }[]; +}; + +const getAtlasDeployment = () => { + if (isKlerosUniversity()) { + return "university"; + } else if (isKlerosNeo()) { + return "beta"; + } else if (isTestnetDeployment()) { + return "testnet"; + } else { + return "devnet"; + } +}; +const atlasUri = import.meta.env.REACT_APP_ATLAS_URI; + +export const useSpamEvidence = () => { + const isEnabled = !isUndefined(atlasUri); + + const variables = { deployment: getAtlasDeployment() }; + return useQuery({ + queryKey: [`evidenceSpamQuery`], + enabled: isEnabled, + staleTime: 60000, + queryFn: async () => await request(`${atlasUri}/graphql`, spamEvidenceQuery, variables), + }); +}; diff --git a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx index 40f32dc4f..870e1888d 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx @@ -8,17 +8,18 @@ import { Button } from "@kleros/ui-components-library"; import DownArrow from "svgs/icons/arrow-down.svg"; +import { useSpamEvidence } from "hooks/useSpamEvidence"; + import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery"; import { useEvidences } from "queries/useEvidences"; import { responsiveSize } from "styles/responsiveSize"; +import { Divider } from "components/Divider"; import EvidenceCard from "components/EvidenceCard"; import { SkeletonEvidenceCard } from "components/StyledSkeleton"; import EvidenceSearch from "./EvidenceSearch"; -import { Divider } from "components/Divider"; -import { spamEvidencesIds } from "src/consts"; const Container = styled.div` width: 100%; @@ -79,6 +80,7 @@ const Evidence: React.FC = () => { const [search, setSearch] = useState(); const [debouncedSearch, setDebouncedSearch] = useState(); const [showSpam, setShowSpam] = useState(false); + const { data: spamEvidences } = useSpamEvidence(); const { data } = useEvidences(disputeData?.dispute?.externalDisputeId?.toString(), debouncedSearch); @@ -93,12 +95,30 @@ const Evidence: React.FC = () => { latestEvidence.scrollIntoView({ behavior: "smooth" }); }, [ref]); + const flattenedSpamEvidences = useMemo( + () => + spamEvidences?.courtv2EvidenceSpamsByDeployment.reduce((acc, current) => { + if (current.dispute === id) { + return [...acc, current.disputeEvidenceIndex]; + } + return acc; + }, []), + [id, spamEvidences] + ); + + const isSpam = useCallback( + (evidenceId: string) => { + return Boolean(flattenedSpamEvidences?.includes(evidenceId)); + }, + [flattenedSpamEvidences] + ); + const evidences = useMemo(() => { if (!data?.evidences) return; const spamEvidences = data.evidences.filter((evidence) => isSpam(evidence.id)); const realEvidences = data.evidences.filter((evidence) => !isSpam(evidence.id)); return { realEvidences, spamEvidences }; - }, [data]); + }, [data, isSpam]); return ( @@ -142,8 +162,4 @@ const Evidence: React.FC = () => { ); }; -const isSpam = (id: string) => { - return spamEvidencesIds.includes(id); -}; - export default Evidence; diff --git a/web/tsconfig.json b/web/tsconfig.json index 8b8b98b97..e4683ef39 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -67,7 +67,8 @@ "resolveJsonModule": true, "target": "ES2020", "lib": [ - "ESNext.Array" + "ESNext.Array", + "dom" ], "types": [ "vite/client", From 01be41e8266b37b710087f44c3825ccdcef3e4ad Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Fri, 13 Dec 2024 16:12:25 +0530 Subject: [PATCH 02/42] chore: rabbit-feedback --- web/src/pages/Cases/CaseDetails/Evidence/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx index 870e1888d..9a433d754 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx @@ -99,7 +99,8 @@ const Evidence: React.FC = () => { () => spamEvidences?.courtv2EvidenceSpamsByDeployment.reduce((acc, current) => { if (current.dispute === id) { - return [...acc, current.disputeEvidenceIndex]; + acc.push(current.disputeEvidenceIndex); + return acc; } return acc; }, []), From 9303c625feabf09ae821246fd08901fccf1f9f7f Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 18 Dec 2024 17:05:59 +0000 Subject: [PATCH 03/42] feat: gated dispute kit --- .../dispute-kits/DisputeKitGated.sol | 639 ++++++++++++++++++ 1 file changed, 639 insertions(+) create mode 100644 contracts/src/arbitration/dispute-kits/DisputeKitGated.sol diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol new file mode 100644 index 000000000..c33bd0f13 --- /dev/null +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: MIT + +/// @custom:authors: [@unknownunknown1, @jaybuidl] +/// @custom:reviewers: [] +/// @custom:auditors: [] +/// @custom:bounties: [] +/// @custom:deployments: [] + +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +import "../KlerosCore.sol"; +import "../interfaces/IDisputeKit.sol"; +import "../../proxy/UUPSProxiable.sol"; +import "../../proxy/Initializable.sol"; + +interface IToken { + /// @dev Returns the number of tokens in `owner` account. + /// @param owner The address of the owner. + /// @return balance The number of tokens in `owner` account. + function balanceOf(address owner) external view returns (uint256 balance); +} + +/// @title DisputeKitGated +/// Dispute kit implementation adapted from DisputeKitClassic +/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate`, +/// - a vote aggregation system: plurality, +/// - an incentive system: equal split between coherent votes, +/// - an appeal system: fund 2 choices only, vote on any choice. +contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { + // ************************************* // + // * Structs * // + // ************************************* // + + struct Dispute { + Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. + uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". + bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. + mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. + bytes extraData; // Extradata for the dispute. + } + + struct Round { + Vote[] votes; // Former votes[_appeal][]. + uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first. + mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`. + bool tied; // True if there is a tie, false otherwise. + uint256 totalVoted; // Former uint[_appeal] votesInEachRound. + uint256 totalCommitted; // Former commitsInRound. + mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round. + mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise. + mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice. + uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. + uint256[] fundedChoices; // Stores the choices that are fully funded. + uint256 nbVotes; // Maximal number of votes this dispute can get. + } + + struct Vote { + address account; // The address of the juror. + bytes32 commit; // The commit of the juror. For courts with hidden votes. + uint256 choice; // The choice of the juror. + bool voted; // True if the vote has been cast. + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. + uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. + uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. + uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. + + address public governor; // The governor of the contract. + KlerosCore public core; // The Kleros Core arbitrator + Dispute[] public disputes; // Array of the locally created disputes. + mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. + IToken public tokenGate; // The token used for gating access. + + // ************************************* // + // * Events * // + // ************************************* // + + /// @dev To be emitted when a dispute is created. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _numberOfChoices The number of choices available in the dispute. + /// @param _extraData The extra data for the dispute. + event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData); + + /// @dev To be emitted when a vote commitment is cast. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _juror The address of the juror casting the vote commitment. + /// @param _voteIDs The identifiers of the votes in the dispute. + /// @param _commit The commitment of the juror. + event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit); + + /// @dev To be emitted when a funding contribution is made. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + /// @param _contributor The address of the contributor. + /// @param _amount The amount contributed. + event Contribution( + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /// @dev To be emitted when the contributed funds are withdrawn. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + /// @param _contributor The address of the contributor. + /// @param _amount The amount withdrawn. + event Withdrawal( + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /// @dev To be emitted when a choice is fully funded for an appeal. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); + + // ************************************* // + // * Modifiers * // + // ************************************* // + + modifier onlyByGovernor() { + require(governor == msg.sender, "Access not allowed: Governor only."); + _; + } + + modifier onlyByCore() { + require(address(core) == msg.sender, "Access not allowed: KlerosCore only."); + _; + } + + modifier notJumped(uint256 _coreDisputeID) { + require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + + // ************************************* // + // * Constructor * // + // ************************************* // + + /// @dev Constructor, initializing the implementation to reduce attack surface. + constructor() { + _disableInitializers(); + } + + /// @dev Initializer. + /// @param _governor The governor's address. + /// @param _core The KlerosCore arbitrator. + /// @param _tokenGate The token used for gating access. + function initialize(address _governor, KlerosCore _core, IToken _tokenGate) external reinitializer(1) { + governor = _governor; + core = _core; + tokenGate = _tokenGate; + } + + // ************************ // + // * Governance * // + // ************************ // + + /// @dev Access Control to perform implementation upgrades (UUPS Proxiable) + /// Only the governor can perform upgrades (`onlyByGovernor`) + function _authorizeUpgrade(address) internal view override onlyByGovernor { + // NOP + } + + /// @dev Allows the governor to call anything on behalf of the contract. + /// @param _destination The destination of the call. + /// @param _amount The value sent with the call. + /// @param _data The data sent with the call. + function executeGovernorProposal( + address _destination, + uint256 _amount, + bytes memory _data + ) external onlyByGovernor { + (bool success, ) = _destination.call{value: _amount}(_data); + require(success, "Unsuccessful call"); + } + + /// @dev Changes the `governor` storage variable. + /// @param _governor The new value for the `governor` storage variable. + function changeGovernor(address payable _governor) external onlyByGovernor { + governor = _governor; + } + + /// @dev Changes the `core` storage variable. + /// @param _core The new value for the `core` storage variable. + function changeCore(address _core) external onlyByGovernor { + core = KlerosCore(_core); + } + + /// @dev Changes the `tokenGate` storage variable. + /// @param _tokenGate The new value for the `tokenGate` storage variable. + function changeTokenGate(address _tokenGate) external onlyByGovernor { + tokenGate = IToken(_tokenGate); + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /// @dev Creates a local dispute and maps it to the dispute ID in the Core contract. + /// Note: Access restricted to Kleros Core only. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _numberOfChoices Number of choices of the dispute + /// @param _extraData Additional info about the dispute, for possible use in future dispute kits. + /// @param _nbVotes Number of votes for this dispute. + function createDispute( + uint256 _coreDisputeID, + uint256 _numberOfChoices, + bytes calldata _extraData, + uint256 _nbVotes + ) external override onlyByCore { + uint256 localDisputeID = disputes.length; + Dispute storage dispute = disputes.push(); + dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + + // New round in the Core should be created before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; + + Round storage round = dispute.rounds.push(); + round.nbVotes = _nbVotes; + round.tied = true; + + coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; + emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData); + } + + /// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. + /// Note: Access restricted to Kleros Core only. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _nonce Nonce of the drawing iteration. + /// @return drawnAddress The drawn address. + function draw( + uint256 _coreDisputeID, + uint256 _nonce + ) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + + ISortitionModule sortitionModule = core.sortitionModule(); + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree. + + drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce); + + if (_postDrawCheck(_coreDisputeID, drawnAddress)) { + round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); + } else { + drawnAddress = address(0); + } + } + + /// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the + /// commit period, each call overrides the commits of the previous one. + /// `O(n)` where + /// `n` is the number of votes. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _voteIDs The IDs of the votes. + /// @param _commit The commit. Note that justification string is a part of the commit. + function castCommit( + uint256 _coreDisputeID, + uint256[] calldata _voteIDs, + bytes32 _commit + ) external notJumped(_coreDisputeID) { + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period."); + require(_commit != bytes32(0), "Empty commit."); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + for (uint256 i = 0; i < _voteIDs.length; i++) { + require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); + round.votes[_voteIDs[i]].commit = _commit; + } + round.totalCommitted += _voteIDs.length; + emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit); + } + + /// @dev Sets the caller's choices for the specified votes. + /// `O(n)` where + /// `n` is the number of votes. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _voteIDs The IDs of the votes. + /// @param _choice The choice. + /// @param _salt The salt for the commit if the votes were hidden. + /// @param _justification Justification of the choice. + function castVote( + uint256 _coreDisputeID, + uint256[] calldata _voteIDs, + uint256 _choice, + uint256 _salt, + string memory _justification + ) external notJumped(_coreDisputeID) { + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period."); + require(_voteIDs.length > 0, "No voteID provided"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); + + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + (, bool hiddenVotes, , , , , ) = core.courts(courtID); + + // Save the votes. + for (uint256 i = 0; i < _voteIDs.length; i++) { + require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); + require( + !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), + "The commit must match the choice in courts with hidden votes." + ); + require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); + round.votes[_voteIDs[i]].choice = _choice; + round.votes[_voteIDs[i]].voted = true; + } + + round.totalVoted += _voteIDs.length; + + round.counts[_choice] += _voteIDs.length; + if (_choice == round.winningChoice) { + if (round.tied) round.tied = false; + } else { + // Voted for another choice. + if (round.counts[_choice] == round.counts[round.winningChoice]) { + // Tie. + if (!round.tied) round.tied = true; + } else if (round.counts[_choice] > round.counts[round.winningChoice]) { + // New winner. + round.winningChoice = _choice; + round.tied = false; + } + } + emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification); + } + + /// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. + /// Note that the surplus deposit will be reimbursed. + /// @param _coreDisputeID Index of the dispute in Kleros Core. + /// @param _choice A choice that receives funding. + function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); + + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); + require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); + + uint256 multiplier; + (uint256 ruling, , ) = this.currentRuling(_coreDisputeID); + if (ruling == _choice) { + multiplier = WINNER_STAKE_MULTIPLIER; + } else { + require( + block.timestamp - appealPeriodStart < + ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT, + "Appeal period is over for loser" + ); + multiplier = LOSER_STAKE_MULTIPLIER; + } + + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; + + require(!round.hasPaid[_choice], "Appeal fee is already paid."); + uint256 appealCost = core.appealCost(_coreDisputeID); + uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; + + // Take up to the amount necessary to fund the current round at the current costs. + uint256 contribution; + if (totalCost > round.paidFees[_choice]) { + contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. + ? msg.value + : totalCost - round.paidFees[_choice]; + emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); + } + + round.contributions[msg.sender][_choice] += contribution; + round.paidFees[_choice] += contribution; + if (round.paidFees[_choice] >= totalCost) { + round.feeRewards += round.paidFees[_choice]; + round.fundedChoices.push(_choice); + round.hasPaid[_choice] = true; + emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); + } + + if (round.fundedChoices.length > 1) { + // At least two sides are fully funded. + round.feeRewards = round.feeRewards - appealCost; + + if (core.isDisputeKitJumping(_coreDisputeID)) { + // Don't create a new round in case of a jump, and remove local dispute from the flow. + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); + newRound.tied = true; + } + core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); + } + + if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); + } + + /// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. + /// Note that withdrawals are not possible if the core contract is paused. + /// @param _coreDisputeID Index of the dispute in Kleros Core contract. + /// @param _beneficiary The address whose rewards to withdraw. + /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from. + /// @param _choice The ruling option that the caller wants to withdraw from. + /// @return amount The withdrawn amount. + function withdrawFeesAndRewards( + uint256 _coreDisputeID, + address payable _beneficiary, + uint256 _coreRoundID, + uint256 _choice + ) external returns (uint256 amount) { + (, , , bool isRuled, ) = core.disputes(_coreDisputeID); + require(isRuled, "Dispute should be resolved."); + require(!core.paused(), "Core is paused"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID); + + if (!round.hasPaid[_choice]) { + // Allow to reimburse if funding was unsuccessful for this ruling option. + amount = round.contributions[_beneficiary][_choice]; + } else { + // Funding was successful for this ruling option. + if (_choice == finalRuling) { + // This ruling option is the ultimate winner. + amount = round.paidFees[_choice] > 0 + ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice] + : 0; + } else if (!round.hasPaid[finalRuling]) { + // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. + amount = + (round.contributions[_beneficiary][_choice] * round.feeRewards) / + (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); + } + } + round.contributions[_beneficiary][_choice] = 0; + + if (amount != 0) { + _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. + emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); + } + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; + return lastRound.fundedChoices; + } + + /// @dev Gets the current ruling of a specified dispute. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return ruling The current ruling. + /// @return tied Whether it's a tie or not. + /// @return overridden Whether the ruling was overridden by appeal funding or not. + function currentRuling( + uint256 _coreDisputeID + ) external view override returns (uint256 ruling, bool tied, bool overridden) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + tied = round.tied; + ruling = tied ? 0 : round.winningChoice; + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + // Override the final ruling if only one side funded the appeals. + if (period == KlerosCoreBase.Period.execution) { + uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID); + if (fundedChoices.length == 1) { + ruling = fundedChoices[0]; + tied = false; + overridden = true; + } + } + } + + /// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the vote. + /// @return The degree of coherence in basis points. + function getDegreeOfCoherence( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID, + uint256 /* _feePerJuror */, + uint256 /* _pnkAtStakePerJuror */ + ) external view override returns (uint256) { + // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); + + if (vote.voted && (vote.choice == winningChoice || tied)) { + return ONE_BASIS_POINT; + } else { + return 0; + } + } + + /// @dev Gets the number of jurors who are eligible to a reward in this round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @return The number of coherent jurors. + function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); + + if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { + return 0; + } else if (tied) { + return currentRound.totalVoted; + } else { + return currentRound.counts[winningChoice]; + } + } + + /// @dev Returns true if all of the jurors have cast their commits for the last round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return Whether all of the jurors have cast their commits for the last round. + function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalCommitted == round.votes.length; + } + + /// @dev Returns true if all of the jurors have cast their votes for the last round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return Whether all of the jurors have cast their votes for the last round. + function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalVoted == round.votes.length; + } + + /// @dev Returns true if the specified voter was active in this round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the voter. + /// @return Whether the voter was active or not. + function isVoteActive( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + return vote.voted; + } + + function getRoundInfo( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _choice + ) + external + view + override + returns ( + uint256 winningChoice, + bool tied, + uint256 totalVoted, + uint256 totalCommited, + uint256 nbVoters, + uint256 choiceCount + ) + { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + return ( + round.winningChoice, + round.tied, + round.totalVoted, + round.totalCommitted, + round.votes.length, + round.counts[_choice] + ); + } + + function getVoteInfo( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + return (vote.account, vote.commit, vote.choice, vote.voted); + } + + // ************************************* // + // * Internal * // + // ************************************* // + + /// @dev Checks that the chosen address satisfies certain conditions for being drawn. + /// @param _coreDisputeID ID of the dispute in the core contract. + /// @param _juror Chosen address. + /// @return Whether the address can be drawn or not. + /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. + /// minStake is checked directly during staking process however it's possible for the juror to get drawn + /// while having < minStake if it is later increased by governance. + /// This issue is expected and harmless since we check for insolvency anyway. + function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) { + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + uint256 lockedAmountPerJuror = core + .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) + .pnkAtStakePerJuror; + (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); + if (totalStaked < totalLocked + lockedAmountPerJuror) { + return false; + } else { + return tokenGate.balanceOf(_juror) > 0; + } + } +} From 3ac630fd0d38f67eb91ea73075398ab1b1c9b41b Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 18 Dec 2024 19:28:09 +0000 Subject: [PATCH 04/42] refactor: base dispute kit --- contracts/src/arbitration/KlerosCore.sol | 5 +- contracts/src/arbitration/KlerosCoreBase.sol | 7 +- contracts/src/arbitration/KlerosCoreNeo.sol | 5 +- contracts/src/arbitration/SortitionModule.sol | 5 +- .../src/arbitration/SortitionModuleBase.sol | 7 +- .../src/arbitration/SortitionModuleNeo.sol | 5 +- .../dispute-kits/DisputeKitClassic.sol | 576 +--------------- .../dispute-kits/DisputeKitClassicBase.sol | 617 ++++++++++++++++++ .../dispute-kits/DisputeKitGated.sol | 582 +---------------- .../dispute-kits/DisputeKitSybilResistant.sol | 590 +---------------- contracts/src/proxy/KlerosProxies.sol | 8 + 11 files changed, 671 insertions(+), 1736 deletions(-) create mode 100644 contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index e3bc02150..16b901485 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -10,12 +10,11 @@ pragma solidity 0.8.24; import "./KlerosCoreBase.sol"; import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; -import {Initializable} from "../proxy/Initializable.sol"; /// @title KlerosCore /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. -contract KlerosCore is KlerosCoreBase, UUPSProxiable, Initializable { +contract KlerosCore is KlerosCoreBase, UUPSProxiable { // ************************************* // // * Constructor * // // ************************************* // @@ -48,7 +47,7 @@ contract KlerosCore is KlerosCoreBase, UUPSProxiable, Initializable { bytes memory _sortitionExtraData, ISortitionModule _sortitionModuleAddress ) external reinitializer(1) { - _initialize( + __KlerosCoreBase_initialize( _governor, _guardian, _pinakion, diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index 2e062e473..604ee1252 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -11,13 +11,14 @@ pragma solidity 0.8.24; import {IArbitrableV2, IArbitratorV2} from "./interfaces/IArbitratorV2.sol"; import {IDisputeKit} from "./interfaces/IDisputeKit.sol"; import {ISortitionModule} from "./interfaces/ISortitionModule.sol"; +import {Initializable} from "../proxy/Initializable.sol"; import {SafeERC20, IERC20} from "../libraries/SafeERC20.sol"; import "../libraries/Constants.sol"; /// @title KlerosCoreBase /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. -abstract contract KlerosCoreBase is IArbitratorV2 { +abstract contract KlerosCoreBase is IArbitratorV2, Initializable { using SafeERC20 for IERC20; // ************************************* // @@ -193,7 +194,7 @@ abstract contract KlerosCoreBase is IArbitratorV2 { // * Constructor * // // ************************************* // - function _initialize( + function __KlerosCoreBase_initialize( address _governor, address _guardian, IERC20 _pinakion, @@ -204,7 +205,7 @@ abstract contract KlerosCoreBase is IArbitratorV2 { uint256[4] memory _timesPerPeriod, bytes memory _sortitionExtraData, ISortitionModule _sortitionModuleAddress - ) internal { + ) internal onlyInitializing { governor = _governor; guardian = _guardian; pinakion = _pinakion; diff --git a/contracts/src/arbitration/KlerosCoreNeo.sol b/contracts/src/arbitration/KlerosCoreNeo.sol index 938bf6d60..32cacdb79 100644 --- a/contracts/src/arbitration/KlerosCoreNeo.sol +++ b/contracts/src/arbitration/KlerosCoreNeo.sol @@ -10,13 +10,12 @@ pragma solidity 0.8.24; import "./KlerosCoreBase.sol"; import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; -import {Initializable} from "../proxy/Initializable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @title KlerosCoreNeo /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. -contract KlerosCoreNeo is KlerosCoreBase, UUPSProxiable, Initializable { +contract KlerosCoreNeo is KlerosCoreBase, UUPSProxiable { // ************************************* // // * Storage * // // ************************************* // @@ -58,7 +57,7 @@ contract KlerosCoreNeo is KlerosCoreBase, UUPSProxiable, Initializable { ISortitionModule _sortitionModuleAddress, IERC721 _jurorNft ) external reinitializer(2) { - super._initialize( + __KlerosCoreBase_initialize( _governor, _guardian, _pinakion, diff --git a/contracts/src/arbitration/SortitionModule.sol b/contracts/src/arbitration/SortitionModule.sol index 4db44c5c4..09ae0c2bb 100644 --- a/contracts/src/arbitration/SortitionModule.sol +++ b/contracts/src/arbitration/SortitionModule.sol @@ -12,11 +12,10 @@ pragma solidity 0.8.24; import "./SortitionModuleBase.sol"; import "../proxy/UUPSProxiable.sol"; -import "../proxy/Initializable.sol"; /// @title SortitionModule /// @dev A factory of trees that keeps track of staked values for sortition. -contract SortitionModule is SortitionModuleBase, UUPSProxiable, Initializable { +contract SortitionModule is SortitionModuleBase, UUPSProxiable { // ************************************* // // * Constructor * // // ************************************* // @@ -41,7 +40,7 @@ contract SortitionModule is SortitionModuleBase, UUPSProxiable, Initializable { RNG _rng, uint256 _rngLookahead ) external reinitializer(1) { - super._initialize(_governor, _core, _minStakingTime, _maxDrawingTime, _rng, _rngLookahead); + __SortitionModuleBase_initialize(_governor, _core, _minStakingTime, _maxDrawingTime, _rng, _rngLookahead); } // ************************************* // diff --git a/contracts/src/arbitration/SortitionModuleBase.sol b/contracts/src/arbitration/SortitionModuleBase.sol index 642f9b627..09ec0733c 100644 --- a/contracts/src/arbitration/SortitionModuleBase.sol +++ b/contracts/src/arbitration/SortitionModuleBase.sol @@ -13,12 +13,13 @@ pragma solidity 0.8.24; import "./KlerosCore.sol"; import "./interfaces/ISortitionModule.sol"; import "./interfaces/IDisputeKit.sol"; +import "../proxy/Initializable.sol"; import "../rng/RNG.sol"; import "../libraries/Constants.sol"; /// @title SortitionModuleBase /// @dev A factory of trees that keeps track of staked values for sortition. -abstract contract SortitionModuleBase is ISortitionModule { +abstract contract SortitionModuleBase is ISortitionModule, Initializable { // ************************************* // // * Enums / Structs * // // ************************************* // @@ -89,14 +90,14 @@ abstract contract SortitionModuleBase is ISortitionModule { // * Constructor * // // ************************************* // - function _initialize( + function __SortitionModuleBase_initialize( address _governor, KlerosCore _core, uint256 _minStakingTime, uint256 _maxDrawingTime, RNG _rng, uint256 _rngLookahead - ) internal { + ) internal onlyInitializing { governor = _governor; core = _core; minStakingTime = _minStakingTime; diff --git a/contracts/src/arbitration/SortitionModuleNeo.sol b/contracts/src/arbitration/SortitionModuleNeo.sol index 0ac13b890..727708d85 100644 --- a/contracts/src/arbitration/SortitionModuleNeo.sol +++ b/contracts/src/arbitration/SortitionModuleNeo.sol @@ -12,11 +12,10 @@ pragma solidity 0.8.24; import "./SortitionModuleBase.sol"; import "../proxy/UUPSProxiable.sol"; -import "../proxy/Initializable.sol"; /// @title SortitionModuleNeo /// @dev A factory of trees that keeps track of staked values for sortition. -contract SortitionModuleNeo is SortitionModuleBase, UUPSProxiable, Initializable { +contract SortitionModuleNeo is SortitionModuleBase, UUPSProxiable { // ************************************* // // * Storage * // // ************************************* // @@ -53,7 +52,7 @@ contract SortitionModuleNeo is SortitionModuleBase, UUPSProxiable, Initializable uint256 _maxStakePerJuror, uint256 _maxTotalStaked ) external reinitializer(2) { - super._initialize(_governor, _core, _minStakingTime, _maxDrawingTime, _rng, _rngLookahead); + __SortitionModuleBase_initialize(_governor, _core, _minStakingTime, _maxDrawingTime, _rng, _rngLookahead); maxStakePerJuror = _maxStakePerJuror; maxTotalStaked = _maxTotalStaked; } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 12d4e45c5..676fb8806 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -8,10 +8,9 @@ pragma solidity 0.8.24; +import "./DisputeKitClassicBase.sol"; import "../KlerosCore.sol"; -import "../interfaces/IDisputeKit.sol"; import "../../proxy/UUPSProxiable.sol"; -import "../../proxy/Initializable.sol"; /// @title DisputeKitClassic /// Dispute kit implementation of the Kleros v1 features including: @@ -19,125 +18,7 @@ import "../../proxy/Initializable.sol"; /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -contract DisputeKitClassic is IDisputeKit, Initializable, UUPSProxiable { - // ************************************* // - // * Structs * // - // ************************************* // - - struct Dispute { - Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. - uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". - bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. - mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. - bytes extraData; // Extradata for the dispute. - } - - struct Round { - Vote[] votes; // Former votes[_appeal][]. - uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first. - mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`. - bool tied; // True if there is a tie, false otherwise. - uint256 totalVoted; // Former uint[_appeal] votesInEachRound. - uint256 totalCommitted; // Former commitsInRound. - mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round. - mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise. - mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice. - uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. - uint256[] fundedChoices; // Stores the choices that are fully funded. - uint256 nbVotes; // Maximal number of votes this dispute can get. - } - - struct Vote { - address account; // The address of the juror. - bytes32 commit; // The commit of the juror. For courts with hidden votes. - uint256 choice; // The choice of the juror. - bool voted; // True if the vote has been cast. - } - - // ************************************* // - // * Storage * // - // ************************************* // - - uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. - uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. - uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. - uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. - - address public governor; // The governor of the contract. - KlerosCore public core; // The Kleros Core arbitrator - Dispute[] public disputes; // Array of the locally created disputes. - mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. - - // ************************************* // - // * Events * // - // ************************************* // - - /// @dev To be emitted when a dispute is created. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _numberOfChoices The number of choices available in the dispute. - /// @param _extraData The extra data for the dispute. - event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData); - - /// @dev To be emitted when a vote commitment is cast. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _juror The address of the juror casting the vote commitment. - /// @param _voteIDs The identifiers of the votes in the dispute. - /// @param _commit The commitment of the juror. - event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit); - - /// @dev To be emitted when a funding contribution is made. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - /// @param _contributor The address of the contributor. - /// @param _amount The amount contributed. - event Contribution( - uint256 indexed _coreDisputeID, - uint256 indexed _coreRoundID, - uint256 _choice, - address indexed _contributor, - uint256 _amount - ); - - /// @dev To be emitted when the contributed funds are withdrawn. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - /// @param _contributor The address of the contributor. - /// @param _amount The amount withdrawn. - event Withdrawal( - uint256 indexed _coreDisputeID, - uint256 indexed _coreRoundID, - uint256 _choice, - address indexed _contributor, - uint256 _amount - ); - - /// @dev To be emitted when a choice is fully funded for an appeal. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); - - // ************************************* // - // * Modifiers * // - // ************************************* // - - modifier onlyByGovernor() { - require(governor == msg.sender, "Access not allowed: Governor only."); - _; - } - - modifier onlyByCore() { - require(address(core) == msg.sender, "Access not allowed: KlerosCore only."); - _; - } - - modifier notJumped(uint256 _coreDisputeID) { - require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); - _; - } - +contract DisputeKitClassic is DisputeKitClassicBase, UUPSProxiable { // ************************************* // // * Constructor * // // ************************************* // @@ -151,8 +32,7 @@ contract DisputeKitClassic is IDisputeKit, Initializable, UUPSProxiable { /// @param _governor The governor's address. /// @param _core The KlerosCore arbitrator. function initialize(address _governor, KlerosCore _core) external reinitializer(1) { - governor = _governor; - core = _core; + __DisputeKitClassicBase_initialize(_governor, _core); } // ************************ // @@ -164,454 +44,4 @@ contract DisputeKitClassic is IDisputeKit, Initializable, UUPSProxiable { function _authorizeUpgrade(address) internal view override onlyByGovernor { // NOP } - - /// @dev Allows the governor to call anything on behalf of the contract. - /// @param _destination The destination of the call. - /// @param _amount The value sent with the call. - /// @param _data The data sent with the call. - function executeGovernorProposal( - address _destination, - uint256 _amount, - bytes memory _data - ) external onlyByGovernor { - (bool success, ) = _destination.call{value: _amount}(_data); - require(success, "Unsuccessful call"); - } - - /// @dev Changes the `governor` storage variable. - /// @param _governor The new value for the `governor` storage variable. - function changeGovernor(address payable _governor) external onlyByGovernor { - governor = _governor; - } - - /// @dev Changes the `core` storage variable. - /// @param _core The new value for the `core` storage variable. - function changeCore(address _core) external onlyByGovernor { - core = KlerosCore(_core); - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /// @dev Creates a local dispute and maps it to the dispute ID in the Core contract. - /// Note: Access restricted to Kleros Core only. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _numberOfChoices Number of choices of the dispute - /// @param _extraData Additional info about the dispute, for possible use in future dispute kits. - /// @param _nbVotes Number of votes for this dispute. - function createDispute( - uint256 _coreDisputeID, - uint256 _numberOfChoices, - bytes calldata _extraData, - uint256 _nbVotes - ) external override onlyByCore { - uint256 localDisputeID = disputes.length; - Dispute storage dispute = disputes.push(); - dispute.numberOfChoices = _numberOfChoices; - dispute.extraData = _extraData; - - // New round in the Core should be created before the dispute creation in DK. - dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; - - Round storage round = dispute.rounds.push(); - round.nbVotes = _nbVotes; - round.tied = true; - - coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; - emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData); - } - - /// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. - /// Note: Access restricted to Kleros Core only. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _nonce Nonce of the drawing iteration. - /// @return drawnAddress The drawn address. - function draw( - uint256 _coreDisputeID, - uint256 _nonce - ) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - - ISortitionModule sortitionModule = core.sortitionModule(); - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree. - - drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce); - - if (_postDrawCheck(_coreDisputeID, drawnAddress)) { - round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); - } else { - drawnAddress = address(0); - } - } - - /// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the - /// commit period, each call overrides the commits of the previous one. - /// `O(n)` where - /// `n` is the number of votes. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _voteIDs The IDs of the votes. - /// @param _commit The commit. Note that justification string is a part of the commit. - function castCommit( - uint256 _coreDisputeID, - uint256[] calldata _voteIDs, - bytes32 _commit - ) external notJumped(_coreDisputeID) { - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period."); - require(_commit != bytes32(0), "Empty commit."); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - for (uint256 i = 0; i < _voteIDs.length; i++) { - require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); - round.votes[_voteIDs[i]].commit = _commit; - } - round.totalCommitted += _voteIDs.length; - emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit); - } - - /// @dev Sets the caller's choices for the specified votes. - /// `O(n)` where - /// `n` is the number of votes. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _voteIDs The IDs of the votes. - /// @param _choice The choice. - /// @param _salt The salt for the commit if the votes were hidden. - /// @param _justification Justification of the choice. - function castVote( - uint256 _coreDisputeID, - uint256[] calldata _voteIDs, - uint256 _choice, - uint256 _salt, - string memory _justification - ) external notJumped(_coreDisputeID) { - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period."); - require(_voteIDs.length > 0, "No voteID provided"); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); - - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - (, bool hiddenVotes, , , , , ) = core.courts(courtID); - - // Save the votes. - for (uint256 i = 0; i < _voteIDs.length; i++) { - require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); - require( - !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), - "The commit must match the choice in courts with hidden votes." - ); - require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); - round.votes[_voteIDs[i]].choice = _choice; - round.votes[_voteIDs[i]].voted = true; - } - - round.totalVoted += _voteIDs.length; - - round.counts[_choice] += _voteIDs.length; - if (_choice == round.winningChoice) { - if (round.tied) round.tied = false; - } else { - // Voted for another choice. - if (round.counts[_choice] == round.counts[round.winningChoice]) { - // Tie. - if (!round.tied) round.tied = true; - } else if (round.counts[_choice] > round.counts[round.winningChoice]) { - // New winner. - round.winningChoice = _choice; - round.tied = false; - } - } - emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification); - } - - /// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. - /// Note that the surplus deposit will be reimbursed. - /// @param _coreDisputeID Index of the dispute in Kleros Core. - /// @param _choice A choice that receives funding. - function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); - - (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); - require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); - - uint256 multiplier; - (uint256 ruling, , ) = this.currentRuling(_coreDisputeID); - if (ruling == _choice) { - multiplier = WINNER_STAKE_MULTIPLIER; - } else { - require( - block.timestamp - appealPeriodStart < - ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT, - "Appeal period is over for loser" - ); - multiplier = LOSER_STAKE_MULTIPLIER; - } - - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; - - require(!round.hasPaid[_choice], "Appeal fee is already paid."); - uint256 appealCost = core.appealCost(_coreDisputeID); - uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; - - // Take up to the amount necessary to fund the current round at the current costs. - uint256 contribution; - if (totalCost > round.paidFees[_choice]) { - contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. - ? msg.value - : totalCost - round.paidFees[_choice]; - emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); - } - - round.contributions[msg.sender][_choice] += contribution; - round.paidFees[_choice] += contribution; - if (round.paidFees[_choice] >= totalCost) { - round.feeRewards += round.paidFees[_choice]; - round.fundedChoices.push(_choice); - round.hasPaid[_choice] = true; - emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); - } - - if (round.fundedChoices.length > 1) { - // At least two sides are fully funded. - round.feeRewards = round.feeRewards - appealCost; - - if (core.isDisputeKitJumping(_coreDisputeID)) { - // Don't create a new round in case of a jump, and remove local dispute from the flow. - dispute.jumped = true; - } else { - // Don't subtract 1 from length since both round arrays haven't been updated yet. - dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; - - Round storage newRound = dispute.rounds.push(); - newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); - newRound.tied = true; - } - core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); - } - - if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); - } - - /// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. - /// Note that withdrawals are not possible if the core contract is paused. - /// @param _coreDisputeID Index of the dispute in Kleros Core contract. - /// @param _beneficiary The address whose rewards to withdraw. - /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from. - /// @param _choice The ruling option that the caller wants to withdraw from. - /// @return amount The withdrawn amount. - function withdrawFeesAndRewards( - uint256 _coreDisputeID, - address payable _beneficiary, - uint256 _coreRoundID, - uint256 _choice - ) external returns (uint256 amount) { - (, , , bool isRuled, ) = core.disputes(_coreDisputeID); - require(isRuled, "Dispute should be resolved."); - require(!core.paused(), "Core is paused"); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - (uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID); - - if (!round.hasPaid[_choice]) { - // Allow to reimburse if funding was unsuccessful for this ruling option. - amount = round.contributions[_beneficiary][_choice]; - } else { - // Funding was successful for this ruling option. - if (_choice == finalRuling) { - // This ruling option is the ultimate winner. - amount = round.paidFees[_choice] > 0 - ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice] - : 0; - } else if (!round.hasPaid[finalRuling]) { - // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. - amount = - (round.contributions[_beneficiary][_choice] * round.feeRewards) / - (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); - } - } - round.contributions[_beneficiary][_choice] = 0; - - if (amount != 0) { - _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. - emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); - } - } - - // ************************************* // - // * Public Views * // - // ************************************* // - - function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - return lastRound.fundedChoices; - } - - /// @dev Gets the current ruling of a specified dispute. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return ruling The current ruling. - /// @return tied Whether it's a tie or not. - /// @return overridden Whether the ruling was overridden by appeal funding or not. - function currentRuling( - uint256 _coreDisputeID - ) external view override returns (uint256 ruling, bool tied, bool overridden) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - tied = round.tied; - ruling = tied ? 0 : round.winningChoice; - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - // Override the final ruling if only one side funded the appeals. - if (period == KlerosCoreBase.Period.execution) { - uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID); - if (fundedChoices.length == 1) { - ruling = fundedChoices[0]; - tied = false; - overridden = true; - } - } - } - - /// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @param _voteID The ID of the vote. - /// @return The degree of coherence in basis points. - function getDegreeOfCoherence( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID, - uint256 /* _feePerJuror */, - uint256 /* _pnkAtStakePerJuror */ - ) external view override returns (uint256) { - // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); - - if (vote.voted && (vote.choice == winningChoice || tied)) { - return ONE_BASIS_POINT; - } else { - return 0; - } - } - - /// @dev Gets the number of jurors who are eligible to a reward in this round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @return The number of coherent jurors. - function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); - - if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { - return 0; - } else if (tied) { - return currentRound.totalVoted; - } else { - return currentRound.counts[winningChoice]; - } - } - - /// @dev Returns true if all of the jurors have cast their commits for the last round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return Whether all of the jurors have cast their commits for the last round. - function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - return round.totalCommitted == round.votes.length; - } - - /// @dev Returns true if all of the jurors have cast their votes for the last round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return Whether all of the jurors have cast their votes for the last round. - function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - return round.totalVoted == round.votes.length; - } - - /// @dev Returns true if the specified voter was active in this round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @param _voteID The ID of the voter. - /// @return Whether the voter was active or not. - function isVoteActive( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID - ) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - return vote.voted; - } - - function getRoundInfo( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _choice - ) - external - view - override - returns ( - uint256 winningChoice, - bool tied, - uint256 totalVoted, - uint256 totalCommited, - uint256 nbVoters, - uint256 choiceCount - ) - { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - return ( - round.winningChoice, - round.tied, - round.totalVoted, - round.totalCommitted, - round.votes.length, - round.counts[_choice] - ); - } - - function getVoteInfo( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID - ) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - return (vote.account, vote.commit, vote.choice, vote.voted); - } - - // ************************************* // - // * Internal * // - // ************************************* // - - /// @dev Checks that the chosen address satisfies certain conditions for being drawn. - /// @param _coreDisputeID ID of the dispute in the core contract. - /// @param _juror Chosen address. - /// @return Whether the address can be drawn or not. - /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. - /// minStake is checked directly during staking process however it's possible for the juror to get drawn - /// while having < minStake if it is later increased by governance. - /// This issue is expected and harmless since we check for insolvency anyway. - function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) { - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - uint256 lockedAmountPerJuror = core - .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) - .pnkAtStakePerJuror; - (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); - return totalStaked >= totalLocked + lockedAmountPerJuror; - } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol new file mode 100644 index 000000000..b59e8dcb4 --- /dev/null +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: MIT + +/// @custom:authors: [@unknownunknown1, @jaybuidl] +/// @custom:reviewers: [] +/// @custom:auditors: [] +/// @custom:bounties: [] +/// @custom:deployments: [] + +pragma solidity 0.8.24; + +import "../KlerosCore.sol"; +import "../interfaces/IDisputeKit.sol"; +import "../../proxy/Initializable.sol"; + +/// @title DisputeKitClassicBase +/// Abstract Dispute kit classic implementation of the Kleros v1 features including: +/// - a drawing system: proportional to staked PNK, +/// - a vote aggregation system: plurality, +/// - an incentive system: equal split between coherent votes, +/// - an appeal system: fund 2 choices only, vote on any choice. +abstract contract DisputeKitClassicBase is IDisputeKit, Initializable { + // ************************************* // + // * Structs * // + // ************************************* // + + struct Dispute { + Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. + uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". + bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. + mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. + bytes extraData; // Extradata for the dispute. + } + + struct Round { + Vote[] votes; // Former votes[_appeal][]. + uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first. + mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`. + bool tied; // True if there is a tie, false otherwise. + uint256 totalVoted; // Former uint[_appeal] votesInEachRound. + uint256 totalCommitted; // Former commitsInRound. + mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round. + mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise. + mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice. + uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. + uint256[] fundedChoices; // Stores the choices that are fully funded. + uint256 nbVotes; // Maximal number of votes this dispute can get. + mapping(address drawnAddress => bool) alreadyDrawn; // Set to 'true' if the address has already been drawn, so it can't be drawn more than once. + } + + struct Vote { + address account; // The address of the juror. + bytes32 commit; // The commit of the juror. For courts with hidden votes. + uint256 choice; // The choice of the juror. + bool voted; // True if the vote has been cast. + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. + uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. + uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. + uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. + + address public governor; // The governor of the contract. + KlerosCore public core; // The Kleros Core arbitrator + Dispute[] public disputes; // Array of the locally created disputes. + mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. + bool public singleDrawPerJuror; // Whether each juror can only draw once per dispute, false by default. + + // ************************************* // + // * Events * // + // ************************************* // + + /// @dev To be emitted when a dispute is created. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _numberOfChoices The number of choices available in the dispute. + /// @param _extraData The extra data for the dispute. + event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData); + + /// @dev To be emitted when a vote commitment is cast. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _juror The address of the juror casting the vote commitment. + /// @param _voteIDs The identifiers of the votes in the dispute. + /// @param _commit The commitment of the juror. + event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit); + + /// @dev To be emitted when a funding contribution is made. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + /// @param _contributor The address of the contributor. + /// @param _amount The amount contributed. + event Contribution( + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /// @dev To be emitted when the contributed funds are withdrawn. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + /// @param _contributor The address of the contributor. + /// @param _amount The amount withdrawn. + event Withdrawal( + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /// @dev To be emitted when a choice is fully funded for an appeal. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); + + // ************************************* // + // * Modifiers * // + // ************************************* // + + modifier onlyByGovernor() { + require(governor == msg.sender, "Access not allowed: Governor only."); + _; + } + + modifier onlyByCore() { + require(address(core) == msg.sender, "Access not allowed: KlerosCore only."); + _; + } + + modifier notJumped(uint256 _coreDisputeID) { + require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + + // ************************************* // + // * Constructor * // + // ************************************* // + + /// @dev Initializer. + /// @param _governor The governor's address. + /// @param _core The KlerosCore arbitrator. + function __DisputeKitClassicBase_initialize(address _governor, KlerosCore _core) internal onlyInitializing { + governor = _governor; + core = _core; + } + + // ************************ // + // * Governance * // + // ************************ // + + /// @dev Allows the governor to call anything on behalf of the contract. + /// @param _destination The destination of the call. + /// @param _amount The value sent with the call. + /// @param _data The data sent with the call. + function executeGovernorProposal( + address _destination, + uint256 _amount, + bytes memory _data + ) external onlyByGovernor { + (bool success, ) = _destination.call{value: _amount}(_data); + require(success, "Unsuccessful call"); + } + + /// @dev Changes the `governor` storage variable. + /// @param _governor The new value for the `governor` storage variable. + function changeGovernor(address payable _governor) external onlyByGovernor { + governor = _governor; + } + + /// @dev Changes the `core` storage variable. + /// @param _core The new value for the `core` storage variable. + function changeCore(address _core) external onlyByGovernor { + core = KlerosCore(_core); + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /// @dev Creates a local dispute and maps it to the dispute ID in the Core contract. + /// Note: Access restricted to Kleros Core only. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _numberOfChoices Number of choices of the dispute + /// @param _extraData Additional info about the dispute, for possible use in future dispute kits. + /// @param _nbVotes Number of votes for this dispute. + function createDispute( + uint256 _coreDisputeID, + uint256 _numberOfChoices, + bytes calldata _extraData, + uint256 _nbVotes + ) external override onlyByCore { + uint256 localDisputeID = disputes.length; + Dispute storage dispute = disputes.push(); + dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + + // New round in the Core should be created before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; + + Round storage round = dispute.rounds.push(); + round.nbVotes = _nbVotes; + round.tied = true; + + coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; + emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData); + } + + /// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. + /// Note: Access restricted to Kleros Core only. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _nonce Nonce of the drawing iteration. + /// @return drawnAddress The drawn address. + function draw( + uint256 _coreDisputeID, + uint256 _nonce + ) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + + ISortitionModule sortitionModule = core.sortitionModule(); + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree. + + drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce); + + if (_postDrawCheck(round, _coreDisputeID, drawnAddress)) { + round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); + round.alreadyDrawn[drawnAddress] = true; + } else { + drawnAddress = address(0); + } + } + + /// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the + /// commit period, each call overrides the commits of the previous one. + /// `O(n)` where + /// `n` is the number of votes. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _voteIDs The IDs of the votes. + /// @param _commit The commit. Note that justification string is a part of the commit. + function castCommit( + uint256 _coreDisputeID, + uint256[] calldata _voteIDs, + bytes32 _commit + ) external notJumped(_coreDisputeID) { + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period."); + require(_commit != bytes32(0), "Empty commit."); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + for (uint256 i = 0; i < _voteIDs.length; i++) { + require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); + round.votes[_voteIDs[i]].commit = _commit; + } + round.totalCommitted += _voteIDs.length; + emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit); + } + + /// @dev Sets the caller's choices for the specified votes. + /// `O(n)` where + /// `n` is the number of votes. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _voteIDs The IDs of the votes. + /// @param _choice The choice. + /// @param _salt The salt for the commit if the votes were hidden. + /// @param _justification Justification of the choice. + function castVote( + uint256 _coreDisputeID, + uint256[] calldata _voteIDs, + uint256 _choice, + uint256 _salt, + string memory _justification + ) external notJumped(_coreDisputeID) { + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period."); + require(_voteIDs.length > 0, "No voteID provided"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); + + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + (, bool hiddenVotes, , , , , ) = core.courts(courtID); + + // Save the votes. + for (uint256 i = 0; i < _voteIDs.length; i++) { + require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); + require( + !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), + "The commit must match the choice in courts with hidden votes." + ); + require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); + round.votes[_voteIDs[i]].choice = _choice; + round.votes[_voteIDs[i]].voted = true; + } + + round.totalVoted += _voteIDs.length; + + round.counts[_choice] += _voteIDs.length; + if (_choice == round.winningChoice) { + if (round.tied) round.tied = false; + } else { + // Voted for another choice. + if (round.counts[_choice] == round.counts[round.winningChoice]) { + // Tie. + if (!round.tied) round.tied = true; + } else if (round.counts[_choice] > round.counts[round.winningChoice]) { + // New winner. + round.winningChoice = _choice; + round.tied = false; + } + } + emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification); + } + + /// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. + /// Note that the surplus deposit will be reimbursed. + /// @param _coreDisputeID Index of the dispute in Kleros Core. + /// @param _choice A choice that receives funding. + function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); + + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); + require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); + + uint256 multiplier; + (uint256 ruling, , ) = this.currentRuling(_coreDisputeID); + if (ruling == _choice) { + multiplier = WINNER_STAKE_MULTIPLIER; + } else { + require( + block.timestamp - appealPeriodStart < + ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT, + "Appeal period is over for loser" + ); + multiplier = LOSER_STAKE_MULTIPLIER; + } + + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; + + require(!round.hasPaid[_choice], "Appeal fee is already paid."); + uint256 appealCost = core.appealCost(_coreDisputeID); + uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; + + // Take up to the amount necessary to fund the current round at the current costs. + uint256 contribution; + if (totalCost > round.paidFees[_choice]) { + contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. + ? msg.value + : totalCost - round.paidFees[_choice]; + emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); + } + + round.contributions[msg.sender][_choice] += contribution; + round.paidFees[_choice] += contribution; + if (round.paidFees[_choice] >= totalCost) { + round.feeRewards += round.paidFees[_choice]; + round.fundedChoices.push(_choice); + round.hasPaid[_choice] = true; + emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); + } + + if (round.fundedChoices.length > 1) { + // At least two sides are fully funded. + round.feeRewards = round.feeRewards - appealCost; + + if (core.isDisputeKitJumping(_coreDisputeID)) { + // Don't create a new round in case of a jump, and remove local dispute from the flow. + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); + newRound.tied = true; + } + core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); + } + + if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); + } + + /// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. + /// Note that withdrawals are not possible if the core contract is paused. + /// @param _coreDisputeID Index of the dispute in Kleros Core contract. + /// @param _beneficiary The address whose rewards to withdraw. + /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from. + /// @param _choice The ruling option that the caller wants to withdraw from. + /// @return amount The withdrawn amount. + function withdrawFeesAndRewards( + uint256 _coreDisputeID, + address payable _beneficiary, + uint256 _coreRoundID, + uint256 _choice + ) external returns (uint256 amount) { + (, , , bool isRuled, ) = core.disputes(_coreDisputeID); + require(isRuled, "Dispute should be resolved."); + require(!core.paused(), "Core is paused"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID); + + if (!round.hasPaid[_choice]) { + // Allow to reimburse if funding was unsuccessful for this ruling option. + amount = round.contributions[_beneficiary][_choice]; + } else { + // Funding was successful for this ruling option. + if (_choice == finalRuling) { + // This ruling option is the ultimate winner. + amount = round.paidFees[_choice] > 0 + ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice] + : 0; + } else if (!round.hasPaid[finalRuling]) { + // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. + amount = + (round.contributions[_beneficiary][_choice] * round.feeRewards) / + (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); + } + } + round.contributions[_beneficiary][_choice] = 0; + + if (amount != 0) { + _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. + emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); + } + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; + return lastRound.fundedChoices; + } + + /// @dev Gets the current ruling of a specified dispute. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return ruling The current ruling. + /// @return tied Whether it's a tie or not. + /// @return overridden Whether the ruling was overridden by appeal funding or not. + function currentRuling( + uint256 _coreDisputeID + ) external view override returns (uint256 ruling, bool tied, bool overridden) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + tied = round.tied; + ruling = tied ? 0 : round.winningChoice; + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + // Override the final ruling if only one side funded the appeals. + if (period == KlerosCoreBase.Period.execution) { + uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID); + if (fundedChoices.length == 1) { + ruling = fundedChoices[0]; + tied = false; + overridden = true; + } + } + } + + /// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the vote. + /// @return The degree of coherence in basis points. + function getDegreeOfCoherence( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID, + uint256 /* _feePerJuror */, + uint256 /* _pnkAtStakePerJuror */ + ) external view override returns (uint256) { + // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); + + if (vote.voted && (vote.choice == winningChoice || tied)) { + return ONE_BASIS_POINT; + } else { + return 0; + } + } + + /// @dev Gets the number of jurors who are eligible to a reward in this round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @return The number of coherent jurors. + function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); + + if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { + return 0; + } else if (tied) { + return currentRound.totalVoted; + } else { + return currentRound.counts[winningChoice]; + } + } + + /// @dev Returns true if all of the jurors have cast their commits for the last round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return Whether all of the jurors have cast their commits for the last round. + function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalCommitted == round.votes.length; + } + + /// @dev Returns true if all of the jurors have cast their votes for the last round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return Whether all of the jurors have cast their votes for the last round. + function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalVoted == round.votes.length; + } + + /// @dev Returns true if the specified voter was active in this round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the voter. + /// @return Whether the voter was active or not. + function isVoteActive( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + return vote.voted; + } + + function getRoundInfo( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _choice + ) + external + view + override + returns ( + uint256 winningChoice, + bool tied, + uint256 totalVoted, + uint256 totalCommited, + uint256 nbVoters, + uint256 choiceCount + ) + { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + return ( + round.winningChoice, + round.tied, + round.totalVoted, + round.totalCommitted, + round.votes.length, + round.counts[_choice] + ); + } + + function getVoteInfo( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + return (vote.account, vote.commit, vote.choice, vote.voted); + } + + // ************************************* // + // * Internal * // + // ************************************* // + + /// @dev Checks that the chosen address satisfies certain conditions for being drawn. + /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. + /// minStake is checked directly during staking process however it's possible for the juror to get drawn + /// while having < minStake if it is later increased by governance. + /// This issue is expected and harmless since we check for insolvency anyway. + /// @param _round The round in which the juror is being drawn. + /// @param _coreDisputeID ID of the dispute in the core contract. + /// @param _juror Chosen address. + /// @return result Whether the address passes the check or not. + function _postDrawCheck( + Round storage _round, + uint256 _coreDisputeID, + address _juror + ) internal view virtual returns (bool result) { + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + uint256 lockedAmountPerJuror = core + .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) + .pnkAtStakePerJuror; + (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); + result = totalStaked >= totalLocked + lockedAmountPerJuror; + + if (singleDrawPerJuror) { + result = result && !_round.alreadyDrawn[_juror]; + } + } +} diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index c33bd0f13..92b5e3e09 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -8,12 +8,9 @@ pragma solidity 0.8.24; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - +import "./DisputeKitClassicBase.sol"; import "../KlerosCore.sol"; -import "../interfaces/IDisputeKit.sol"; import "../../proxy/UUPSProxiable.sol"; -import "../../proxy/Initializable.sol"; interface IToken { /// @dev Returns the number of tokens in `owner` account. @@ -28,126 +25,13 @@ interface IToken { /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { - // ************************************* // - // * Structs * // - // ************************************* // - - struct Dispute { - Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. - uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". - bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. - mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. - bytes extraData; // Extradata for the dispute. - } - - struct Round { - Vote[] votes; // Former votes[_appeal][]. - uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first. - mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`. - bool tied; // True if there is a tie, false otherwise. - uint256 totalVoted; // Former uint[_appeal] votesInEachRound. - uint256 totalCommitted; // Former commitsInRound. - mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round. - mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise. - mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice. - uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. - uint256[] fundedChoices; // Stores the choices that are fully funded. - uint256 nbVotes; // Maximal number of votes this dispute can get. - } - - struct Vote { - address account; // The address of the juror. - bytes32 commit; // The commit of the juror. For courts with hidden votes. - uint256 choice; // The choice of the juror. - bool voted; // True if the vote has been cast. - } - +contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { // ************************************* // // * Storage * // // ************************************* // - uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. - uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. - uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. - uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. - - address public governor; // The governor of the contract. - KlerosCore public core; // The Kleros Core arbitrator - Dispute[] public disputes; // Array of the locally created disputes. - mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. IToken public tokenGate; // The token used for gating access. - // ************************************* // - // * Events * // - // ************************************* // - - /// @dev To be emitted when a dispute is created. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _numberOfChoices The number of choices available in the dispute. - /// @param _extraData The extra data for the dispute. - event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData); - - /// @dev To be emitted when a vote commitment is cast. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _juror The address of the juror casting the vote commitment. - /// @param _voteIDs The identifiers of the votes in the dispute. - /// @param _commit The commitment of the juror. - event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit); - - /// @dev To be emitted when a funding contribution is made. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - /// @param _contributor The address of the contributor. - /// @param _amount The amount contributed. - event Contribution( - uint256 indexed _coreDisputeID, - uint256 indexed _coreRoundID, - uint256 _choice, - address indexed _contributor, - uint256 _amount - ); - - /// @dev To be emitted when the contributed funds are withdrawn. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - /// @param _contributor The address of the contributor. - /// @param _amount The amount withdrawn. - event Withdrawal( - uint256 indexed _coreDisputeID, - uint256 indexed _coreRoundID, - uint256 _choice, - address indexed _contributor, - uint256 _amount - ); - - /// @dev To be emitted when a choice is fully funded for an appeal. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); - - // ************************************* // - // * Modifiers * // - // ************************************* // - - modifier onlyByGovernor() { - require(governor == msg.sender, "Access not allowed: Governor only."); - _; - } - - modifier onlyByCore() { - require(address(core) == msg.sender, "Access not allowed: KlerosCore only."); - _; - } - - modifier notJumped(uint256 _coreDisputeID) { - require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); - _; - } - // ************************************* // // * Constructor * // // ************************************* // @@ -162,8 +46,7 @@ contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { /// @param _core The KlerosCore arbitrator. /// @param _tokenGate The token used for gating access. function initialize(address _governor, KlerosCore _core, IToken _tokenGate) external reinitializer(1) { - governor = _governor; - core = _core; + __DisputeKitClassicBase_initialize(_governor, _core); tokenGate = _tokenGate; } @@ -177,463 +60,24 @@ contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { // NOP } - /// @dev Allows the governor to call anything on behalf of the contract. - /// @param _destination The destination of the call. - /// @param _amount The value sent with the call. - /// @param _data The data sent with the call. - function executeGovernorProposal( - address _destination, - uint256 _amount, - bytes memory _data - ) external onlyByGovernor { - (bool success, ) = _destination.call{value: _amount}(_data); - require(success, "Unsuccessful call"); - } - - /// @dev Changes the `governor` storage variable. - /// @param _governor The new value for the `governor` storage variable. - function changeGovernor(address payable _governor) external onlyByGovernor { - governor = _governor; - } - - /// @dev Changes the `core` storage variable. - /// @param _core The new value for the `core` storage variable. - function changeCore(address _core) external onlyByGovernor { - core = KlerosCore(_core); - } - - /// @dev Changes the `tokenGate` storage variable. - /// @param _tokenGate The new value for the `tokenGate` storage variable. - function changeTokenGate(address _tokenGate) external onlyByGovernor { - tokenGate = IToken(_tokenGate); - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /// @dev Creates a local dispute and maps it to the dispute ID in the Core contract. - /// Note: Access restricted to Kleros Core only. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _numberOfChoices Number of choices of the dispute - /// @param _extraData Additional info about the dispute, for possible use in future dispute kits. - /// @param _nbVotes Number of votes for this dispute. - function createDispute( - uint256 _coreDisputeID, - uint256 _numberOfChoices, - bytes calldata _extraData, - uint256 _nbVotes - ) external override onlyByCore { - uint256 localDisputeID = disputes.length; - Dispute storage dispute = disputes.push(); - dispute.numberOfChoices = _numberOfChoices; - dispute.extraData = _extraData; - - // New round in the Core should be created before the dispute creation in DK. - dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; - - Round storage round = dispute.rounds.push(); - round.nbVotes = _nbVotes; - round.tied = true; - - coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; - emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData); - } - - /// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. - /// Note: Access restricted to Kleros Core only. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _nonce Nonce of the drawing iteration. - /// @return drawnAddress The drawn address. - function draw( - uint256 _coreDisputeID, - uint256 _nonce - ) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - - ISortitionModule sortitionModule = core.sortitionModule(); - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree. - - drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce); - - if (_postDrawCheck(_coreDisputeID, drawnAddress)) { - round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); - } else { - drawnAddress = address(0); - } - } - - /// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the - /// commit period, each call overrides the commits of the previous one. - /// `O(n)` where - /// `n` is the number of votes. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _voteIDs The IDs of the votes. - /// @param _commit The commit. Note that justification string is a part of the commit. - function castCommit( - uint256 _coreDisputeID, - uint256[] calldata _voteIDs, - bytes32 _commit - ) external notJumped(_coreDisputeID) { - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period."); - require(_commit != bytes32(0), "Empty commit."); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - for (uint256 i = 0; i < _voteIDs.length; i++) { - require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); - round.votes[_voteIDs[i]].commit = _commit; - } - round.totalCommitted += _voteIDs.length; - emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit); - } - - /// @dev Sets the caller's choices for the specified votes. - /// `O(n)` where - /// `n` is the number of votes. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _voteIDs The IDs of the votes. - /// @param _choice The choice. - /// @param _salt The salt for the commit if the votes were hidden. - /// @param _justification Justification of the choice. - function castVote( - uint256 _coreDisputeID, - uint256[] calldata _voteIDs, - uint256 _choice, - uint256 _salt, - string memory _justification - ) external notJumped(_coreDisputeID) { - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period."); - require(_voteIDs.length > 0, "No voteID provided"); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); - - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - (, bool hiddenVotes, , , , , ) = core.courts(courtID); - - // Save the votes. - for (uint256 i = 0; i < _voteIDs.length; i++) { - require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); - require( - !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), - "The commit must match the choice in courts with hidden votes." - ); - require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); - round.votes[_voteIDs[i]].choice = _choice; - round.votes[_voteIDs[i]].voted = true; - } - - round.totalVoted += _voteIDs.length; - - round.counts[_choice] += _voteIDs.length; - if (_choice == round.winningChoice) { - if (round.tied) round.tied = false; - } else { - // Voted for another choice. - if (round.counts[_choice] == round.counts[round.winningChoice]) { - // Tie. - if (!round.tied) round.tied = true; - } else if (round.counts[_choice] > round.counts[round.winningChoice]) { - // New winner. - round.winningChoice = _choice; - round.tied = false; - } - } - emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification); - } - - /// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. - /// Note that the surplus deposit will be reimbursed. - /// @param _coreDisputeID Index of the dispute in Kleros Core. - /// @param _choice A choice that receives funding. - function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); - - (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); - require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); - - uint256 multiplier; - (uint256 ruling, , ) = this.currentRuling(_coreDisputeID); - if (ruling == _choice) { - multiplier = WINNER_STAKE_MULTIPLIER; - } else { - require( - block.timestamp - appealPeriodStart < - ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT, - "Appeal period is over for loser" - ); - multiplier = LOSER_STAKE_MULTIPLIER; - } - - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; - - require(!round.hasPaid[_choice], "Appeal fee is already paid."); - uint256 appealCost = core.appealCost(_coreDisputeID); - uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; - - // Take up to the amount necessary to fund the current round at the current costs. - uint256 contribution; - if (totalCost > round.paidFees[_choice]) { - contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. - ? msg.value - : totalCost - round.paidFees[_choice]; - emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); - } - - round.contributions[msg.sender][_choice] += contribution; - round.paidFees[_choice] += contribution; - if (round.paidFees[_choice] >= totalCost) { - round.feeRewards += round.paidFees[_choice]; - round.fundedChoices.push(_choice); - round.hasPaid[_choice] = true; - emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); - } - - if (round.fundedChoices.length > 1) { - // At least two sides are fully funded. - round.feeRewards = round.feeRewards - appealCost; - - if (core.isDisputeKitJumping(_coreDisputeID)) { - // Don't create a new round in case of a jump, and remove local dispute from the flow. - dispute.jumped = true; - } else { - // Don't subtract 1 from length since both round arrays haven't been updated yet. - dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; - - Round storage newRound = dispute.rounds.push(); - newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); - newRound.tied = true; - } - core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); - } - - if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); - } - - /// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. - /// Note that withdrawals are not possible if the core contract is paused. - /// @param _coreDisputeID Index of the dispute in Kleros Core contract. - /// @param _beneficiary The address whose rewards to withdraw. - /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from. - /// @param _choice The ruling option that the caller wants to withdraw from. - /// @return amount The withdrawn amount. - function withdrawFeesAndRewards( - uint256 _coreDisputeID, - address payable _beneficiary, - uint256 _coreRoundID, - uint256 _choice - ) external returns (uint256 amount) { - (, , , bool isRuled, ) = core.disputes(_coreDisputeID); - require(isRuled, "Dispute should be resolved."); - require(!core.paused(), "Core is paused"); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - (uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID); - - if (!round.hasPaid[_choice]) { - // Allow to reimburse if funding was unsuccessful for this ruling option. - amount = round.contributions[_beneficiary][_choice]; - } else { - // Funding was successful for this ruling option. - if (_choice == finalRuling) { - // This ruling option is the ultimate winner. - amount = round.paidFees[_choice] > 0 - ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice] - : 0; - } else if (!round.hasPaid[finalRuling]) { - // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. - amount = - (round.contributions[_beneficiary][_choice] * round.feeRewards) / - (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); - } - } - round.contributions[_beneficiary][_choice] = 0; - - if (amount != 0) { - _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. - emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); - } - } - - // ************************************* // - // * Public Views * // - // ************************************* // - - function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - return lastRound.fundedChoices; - } - - /// @dev Gets the current ruling of a specified dispute. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return ruling The current ruling. - /// @return tied Whether it's a tie or not. - /// @return overridden Whether the ruling was overridden by appeal funding or not. - function currentRuling( - uint256 _coreDisputeID - ) external view override returns (uint256 ruling, bool tied, bool overridden) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - tied = round.tied; - ruling = tied ? 0 : round.winningChoice; - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - // Override the final ruling if only one side funded the appeals. - if (period == KlerosCoreBase.Period.execution) { - uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID); - if (fundedChoices.length == 1) { - ruling = fundedChoices[0]; - tied = false; - overridden = true; - } - } - } - - /// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @param _voteID The ID of the vote. - /// @return The degree of coherence in basis points. - function getDegreeOfCoherence( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID, - uint256 /* _feePerJuror */, - uint256 /* _pnkAtStakePerJuror */ - ) external view override returns (uint256) { - // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); - - if (vote.voted && (vote.choice == winningChoice || tied)) { - return ONE_BASIS_POINT; - } else { - return 0; - } - } - - /// @dev Gets the number of jurors who are eligible to a reward in this round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @return The number of coherent jurors. - function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); - - if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { - return 0; - } else if (tied) { - return currentRound.totalVoted; - } else { - return currentRound.counts[winningChoice]; - } - } - - /// @dev Returns true if all of the jurors have cast their commits for the last round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return Whether all of the jurors have cast their commits for the last round. - function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - return round.totalCommitted == round.votes.length; - } - - /// @dev Returns true if all of the jurors have cast their votes for the last round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return Whether all of the jurors have cast their votes for the last round. - function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - return round.totalVoted == round.votes.length; - } - - /// @dev Returns true if the specified voter was active in this round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @param _voteID The ID of the voter. - /// @return Whether the voter was active or not. - function isVoteActive( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID - ) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - return vote.voted; - } - - function getRoundInfo( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _choice - ) - external - view - override - returns ( - uint256 winningChoice, - bool tied, - uint256 totalVoted, - uint256 totalCommited, - uint256 nbVoters, - uint256 choiceCount - ) - { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - return ( - round.winningChoice, - round.tied, - round.totalVoted, - round.totalCommitted, - round.votes.length, - round.counts[_choice] - ); - } - - function getVoteInfo( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID - ) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - return (vote.account, vote.commit, vote.choice, vote.voted); - } - // ************************************* // // * Internal * // // ************************************* // /// @dev Checks that the chosen address satisfies certain conditions for being drawn. - /// @param _coreDisputeID ID of the dispute in the core contract. - /// @param _juror Chosen address. - /// @return Whether the address can be drawn or not. /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. /// minStake is checked directly during staking process however it's possible for the juror to get drawn /// while having < minStake if it is later increased by governance. /// This issue is expected and harmless since we check for insolvency anyway. - function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) { - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - uint256 lockedAmountPerJuror = core - .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) - .pnkAtStakePerJuror; - (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); - if (totalStaked < totalLocked + lockedAmountPerJuror) { - return false; - } else { - return tokenGate.balanceOf(_juror) > 0; - } + /// @param _round The round in which the juror is being drawn. + /// @param _coreDisputeID ID of the dispute in the core contract. + /// @param _juror Chosen address. + /// @return result Whether the address passes the check or not. + function _postDrawCheck( + Round storage _round, + uint256 _coreDisputeID, + address _juror + ) internal view override returns (bool) { + return super._postDrawCheck(_round, _coreDisputeID, _juror) && tokenGate.balanceOf(_juror) > 0; } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index dc571cfbf..dc076acc3 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -8,10 +8,9 @@ pragma solidity 0.8.24; +import "./DisputeKitClassicBase.sol"; import "../KlerosCore.sol"; -import "../interfaces/IDisputeKit.sol"; import "../../proxy/UUPSProxiable.sol"; -import "../../proxy/Initializable.sol"; interface IProofOfHumanity { /// @dev Return true if the submission is registered and not expired. @@ -26,127 +25,13 @@ interface IProofOfHumanity { /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -contract DisputeKitSybilResistant is IDisputeKit, Initializable, UUPSProxiable { - // ************************************* // - // * Structs * // - // ************************************* // - - struct Dispute { - Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. - uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". - bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. - mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. - bytes extraData; // Extradata for the dispute. - } - - struct Round { - Vote[] votes; // Former votes[_appeal][]. - uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first. - mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`. - bool tied; // True if there is a tie, false otherwise. - uint256 totalVoted; // Former uint[_appeal] votesInEachRound. - uint256 totalCommitted; // Former commitsInRound. - mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round. - mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise. - mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice. - uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. - uint256[] fundedChoices; // Stores the choices that are fully funded. - uint256 nbVotes; // Maximal number of votes this dispute can get. - mapping(address drawnAddress => bool) alreadyDrawn; // Set to 'true' if the address has already been drawn, so it can't be drawn more than once. - } - - struct Vote { - address account; // The address of the juror. - bytes32 commit; // The commit of the juror. For courts with hidden votes. - uint256 choice; // The choice of the juror. - bool voted; // True if the vote has been cast. - } - +contract DisputeKitSybilResistant is DisputeKitClassicBase, UUPSProxiable { // ************************************* // // * Storage * // // ************************************* // - uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. - uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. - uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. - uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. - - address public governor; // The governor of the contract. - KlerosCore public core; // The Kleros Core arbitrator - Dispute[] public disputes; // Array of the locally created disputes. - mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. IProofOfHumanity public poh; // The Proof of Humanity registry - // ************************************* // - // * Events * // - // ************************************* // - - /// @dev To be emitted when a dispute is created. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _numberOfChoices The number of choices available in the dispute. - /// @param _extraData The extra data for the dispute. - event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData); - - /// @dev To be emitted when a vote commitment is cast. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _juror The address of the juror casting the vote commitment. - /// @param _voteIDs The identifiers of the votes in the dispute. - /// @param _commit The commitment of the juror. - event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit); - - /// @dev To be emitted when a funding contribution is made. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - /// @param _contributor The address of the contributor. - /// @param _amount The amount contributed. - event Contribution( - uint256 indexed _coreDisputeID, - uint256 indexed _coreRoundID, - uint256 _choice, - address indexed _contributor, - uint256 _amount - ); - - /// @dev To be emitted when the contributed funds are withdrawn. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - /// @param _contributor The address of the contributor. - /// @param _amount The amount withdrawn. - event Withdrawal( - uint256 indexed _coreDisputeID, - uint256 indexed _coreRoundID, - uint256 _choice, - address indexed _contributor, - uint256 _amount - ); - - /// @dev To be emitted when a choice is fully funded for an appeal. - /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. - /// @param _coreRoundID The identifier of the round in the Arbitrator contract. - /// @param _choice The choice that is being funded. - event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); - - // ************************************* // - // * Modifiers * // - // ************************************* // - - modifier onlyByGovernor() { - require(governor == msg.sender, "Access not allowed: Governor only."); - _; - } - - modifier onlyByCore() { - require(address(core) == msg.sender, "Access not allowed: KlerosCore only."); - _; - } - - modifier notJumped(uint256 _coreDisputeID) { - require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); - _; - } - // ************************************* // // * Constructor * // // ************************************* // @@ -161,9 +46,9 @@ contract DisputeKitSybilResistant is IDisputeKit, Initializable, UUPSProxiable { /// @param _core The KlerosCore arbitrator. /// @param _poh The Proof of Humanity registry. function initialize(address _governor, KlerosCore _core, IProofOfHumanity _poh) external reinitializer(1) { - governor = _governor; - core = _core; + __DisputeKitClassicBase_initialize(_governor, _core); poh = _poh; + singleDrawPerJuror = true; } // ************************ // @@ -176,471 +61,24 @@ contract DisputeKitSybilResistant is IDisputeKit, Initializable, UUPSProxiable { // NOP } - /// @dev Allows the governor to call anything on behalf of the contract. - /// @param _destination The destination of the call. - /// @param _amount The value sent with the call. - /// @param _data The data sent with the call. - function executeGovernorProposal( - address _destination, - uint256 _amount, - bytes memory _data - ) external onlyByGovernor { - (bool success, ) = _destination.call{value: _amount}(_data); - require(success, "Unsuccessful call"); - } - - /// @dev Changes the `governor` storage variable. - /// @param _governor The new value for the `governor` storage variable. - function changeGovernor(address payable _governor) external onlyByGovernor { - governor = _governor; - } - - /// @dev Changes the `core` storage variable. - /// @param _core The new value for the `core` storage variable. - function changeCore(address _core) external onlyByGovernor { - core = KlerosCore(_core); - } - - /// @dev Changes the `poh` storage variable. - /// @param _poh The new value for the `poh` storage variable. - function changePoh(address _poh) external onlyByGovernor { - poh = IProofOfHumanity(_poh); - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /// @dev Creates a local dispute and maps it to the dispute ID in the Core contract. - /// Note: Access restricted to Kleros Core only. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _numberOfChoices Number of choices of the dispute - /// @param _extraData Additional info about the dispute, for possible use in future dispute kits. - /// @param _nbVotes Number of votes for this dispute. - function createDispute( - uint256 _coreDisputeID, - uint256 _numberOfChoices, - bytes calldata _extraData, - uint256 _nbVotes - ) external override onlyByCore { - uint256 localDisputeID = disputes.length; - Dispute storage dispute = disputes.push(); - dispute.numberOfChoices = _numberOfChoices; - dispute.extraData = _extraData; - - // New round in the Core should be created before the dispute creation in DK. - dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; - - Round storage round = dispute.rounds.push(); - round.nbVotes = _nbVotes; - round.tied = true; - - coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; - emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData); - } - - /// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. - /// Note: Access restricted to Kleros Core only. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _nonce Nonce of the drawing iteration. - /// @return drawnAddress The drawn address. - function draw( - uint256 _coreDisputeID, - uint256 _nonce - ) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - - ISortitionModule sortitionModule = core.sortitionModule(); - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree. - - drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce); - - if (_postDrawCheck(_coreDisputeID, drawnAddress) && !round.alreadyDrawn[drawnAddress]) { - round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); - round.alreadyDrawn[drawnAddress] = true; - } else { - drawnAddress = address(0); - } - } - - /// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the - /// commit period, each call overrides the commits of the previous one. - /// `O(n)` where - /// `n` is the number of votes. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _voteIDs The IDs of the votes. - /// @param _commit The commit. Note that justification string is a part of the commit. - function castCommit( - uint256 _coreDisputeID, - uint256[] calldata _voteIDs, - bytes32 _commit - ) external notJumped(_coreDisputeID) { - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period."); - require(_commit != bytes32(0), "Empty commit."); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - for (uint256 i = 0; i < _voteIDs.length; i++) { - require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); - round.votes[_voteIDs[i]].commit = _commit; - } - round.totalCommitted += _voteIDs.length; - emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit); - } - - /// @dev Sets the caller's choices for the specified votes. - /// `O(n)` where - /// `n` is the number of votes. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @param _voteIDs The IDs of the votes. - /// @param _choice The choice. - /// @param _salt The salt for the commit if the votes were hidden. - /// @param _justification Justification of the choice. - function castVote( - uint256 _coreDisputeID, - uint256[] calldata _voteIDs, - uint256 _choice, - uint256 _salt, - string memory _justification - ) external notJumped(_coreDisputeID) { - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period."); - require(_voteIDs.length > 0, "No voteID provided"); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); - - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - (, bool hiddenVotes, , , , , ) = core.courts(courtID); - - // Save the votes. - for (uint256 i = 0; i < _voteIDs.length; i++) { - require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); - require( - !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), - "The commit must match the choice in courts with hidden votes." - ); - require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); - round.votes[_voteIDs[i]].choice = _choice; - round.votes[_voteIDs[i]].voted = true; - } - - round.totalVoted += _voteIDs.length; - - round.counts[_choice] += _voteIDs.length; - if (_choice == round.winningChoice) { - if (round.tied) round.tied = false; - } else { - // Voted for another choice. - if (round.counts[_choice] == round.counts[round.winningChoice]) { - // Tie. - if (!round.tied) round.tied = true; - } else if (round.counts[_choice] > round.counts[round.winningChoice]) { - // New winner. - round.winningChoice = _choice; - round.tied = false; - } - } - emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification); - } - - /// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. - /// Note that the surplus deposit will be reimbursed. - /// @param _coreDisputeID Index of the dispute in Kleros Core. - /// @param _choice A choice that receives funding. - function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); - - (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); - require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); - - uint256 multiplier; - (uint256 ruling, , ) = this.currentRuling(_coreDisputeID); - if (ruling == _choice) { - multiplier = WINNER_STAKE_MULTIPLIER; - } else { - require( - block.timestamp - appealPeriodStart < - ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT, - "Appeal period is over for loser" - ); - multiplier = LOSER_STAKE_MULTIPLIER; - } - - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; - - require(!round.hasPaid[_choice], "Appeal fee is already paid."); - uint256 appealCost = core.appealCost(_coreDisputeID); - uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; - - // Take up to the amount necessary to fund the current round at the current costs. - uint256 contribution; - if (totalCost > round.paidFees[_choice]) { - contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. - ? msg.value - : totalCost - round.paidFees[_choice]; - emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); - } - - round.contributions[msg.sender][_choice] += contribution; - round.paidFees[_choice] += contribution; - if (round.paidFees[_choice] >= totalCost) { - round.feeRewards += round.paidFees[_choice]; - round.fundedChoices.push(_choice); - round.hasPaid[_choice] = true; - emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); - } - - if (round.fundedChoices.length > 1) { - // At least two sides are fully funded. - round.feeRewards = round.feeRewards - appealCost; - - if (core.isDisputeKitJumping(_coreDisputeID)) { - // Don't create a new round in case of a jump, and remove local dispute from the flow. - dispute.jumped = true; - } else { - // Don't subtract 1 from length since both round arrays haven't been updated yet. - dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; - - Round storage newRound = dispute.rounds.push(); - newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); - newRound.tied = true; - } - core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); - } - - if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); - } - - /// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. - /// Note that withdrawals are not possible if the core contract is paused. - /// @param _coreDisputeID Index of the dispute in Kleros Core contract. - /// @param _beneficiary The address whose rewards to withdraw. - /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from. - /// @param _choice The ruling option that the caller wants to withdraw from. - /// @return amount The withdrawn amount. - function withdrawFeesAndRewards( - uint256 _coreDisputeID, - address payable _beneficiary, - uint256 _coreRoundID, - uint256 _choice - ) external returns (uint256 amount) { - (, , , bool isRuled, ) = core.disputes(_coreDisputeID); - require(isRuled, "Dispute should be resolved."); - require(!core.paused(), "Core is paused"); - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - (uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID); - - if (!round.hasPaid[_choice]) { - // Allow to reimburse if funding was unsuccessful for this ruling option. - amount = round.contributions[_beneficiary][_choice]; - } else { - // Funding was successful for this ruling option. - if (_choice == finalRuling) { - // This ruling option is the ultimate winner. - amount = round.paidFees[_choice] > 0 - ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice] - : 0; - } else if (!round.hasPaid[finalRuling]) { - // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. - amount = - (round.contributions[_beneficiary][_choice] * round.feeRewards) / - (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); - } - } - round.contributions[_beneficiary][_choice] = 0; - - if (amount != 0) { - _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. - emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); - } - } - - // ************************************* // - // * Public Views * // - // ************************************* // - - function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - return lastRound.fundedChoices; - } - - /// @dev Gets the current ruling of a specified dispute. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return ruling The current ruling. - /// @return tied Whether it's a tie or not. - /// @return overridden Whether the ruling was overridden by appeal funding or not. - function currentRuling( - uint256 _coreDisputeID - ) external view override returns (uint256 ruling, bool tied, bool overridden) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - tied = round.tied; - ruling = tied ? 0 : round.winningChoice; - (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); - // Override the final ruling if only one side funded the appeals. - if (period == KlerosCoreBase.Period.execution) { - uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID); - if (fundedChoices.length == 1) { - ruling = fundedChoices[0]; - tied = false; - overridden = true; - } - } - } - - /// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @param _voteID The ID of the vote. - /// @return The degree of coherence in basis points. - function getDegreeOfCoherence( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID, - uint256 /* _feePerJuror */, - uint256 /* _pnkAtStakePerJuror */ - ) external view override returns (uint256) { - // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); - - if (vote.voted && (vote.choice == winningChoice || tied)) { - return ONE_BASIS_POINT; - } else { - return 0; - } - } - - /// @dev Gets the number of jurors who are eligible to a reward in this round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @return The number of coherent jurors. - function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); - - if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { - return 0; - } else if (tied) { - return currentRound.totalVoted; - } else { - return currentRound.counts[winningChoice]; - } - } - - /// @dev Returns true if all of the jurors have cast their commits for the last round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return Whether all of the jurors have cast their commits for the last round. - function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - return round.totalCommitted == round.votes.length; - } - - /// @dev Returns true if all of the jurors have cast their votes for the last round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core. - /// @return Whether all of the jurors have cast their votes for the last round. - function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - return round.totalVoted == round.votes.length; - } - - /// @dev Returns true if the specified voter was active in this round. - /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. - /// @param _voteID The ID of the voter. - /// @return Whether the voter was active or not. - function isVoteActive( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID - ) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - return vote.voted; - } - - function getRoundInfo( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _choice - ) - external - view - override - returns ( - uint256 winningChoice, - bool tied, - uint256 totalVoted, - uint256 totalCommited, - uint256 nbVoters, - uint256 choiceCount - ) - { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; - return ( - round.winningChoice, - round.tied, - round.totalVoted, - round.totalCommitted, - round.votes.length, - round.counts[_choice] - ); - } - - function getVoteInfo( - uint256 _coreDisputeID, - uint256 _coreRoundID, - uint256 _voteID - ) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; - return (vote.account, vote.commit, vote.choice, vote.voted); - } - // ************************************* // // * Internal * // // ************************************* // /// @dev Checks that the chosen address satisfies certain conditions for being drawn. - /// @param _coreDisputeID ID of the dispute in the core contract. - /// @param _juror Chosen address. - /// @return Whether the address can be drawn or not. /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. /// minStake is checked directly during staking process however it's possible for the juror to get drawn /// while having < minStake if it is later increased by governance. /// This issue is expected and harmless since we check for insolvency anyway. - function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) { - (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); - uint256 lockedAmountPerJuror = core - .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) - .pnkAtStakePerJuror; - (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); - if (totalStaked < totalLocked + lockedAmountPerJuror) { - return false; - } else { - return _proofOfHumanity(_juror); - } - } - - /// @dev Checks if an address belongs to the Proof of Humanity registry. - /// @param _address The address to check. - /// @return registered True if registered. - function _proofOfHumanity(address _address) internal view returns (bool) { - return poh.isRegistered(_address); + /// @param _round The round in which the juror is being drawn. + /// @param _coreDisputeID ID of the dispute in the core contract. + /// @param _juror Chosen address. + /// @return result Whether the address can be drawn or not. + function _postDrawCheck( + Round storage _round, + uint256 _coreDisputeID, + address _juror + ) internal view override returns (bool) { + return super._postDrawCheck(_round, _coreDisputeID, _juror) && poh.isRegistered(_juror); } } diff --git a/contracts/src/proxy/KlerosProxies.sol b/contracts/src/proxy/KlerosProxies.sol index 7c2bb2ddb..2a1a9381f 100644 --- a/contracts/src/proxy/KlerosProxies.sol +++ b/contracts/src/proxy/KlerosProxies.sol @@ -19,6 +19,14 @@ contract DisputeKitClassicProxy is UUPSProxy { constructor(address _implementation, bytes memory _data) UUPSProxy(_implementation, _data) {} } +contract DisputeKitGatedProxy is UUPSProxy { + constructor(address _implementation, bytes memory _data) UUPSProxy(_implementation, _data) {} +} + +contract DisputeKitSybilResistantProxy is UUPSProxy { + constructor(address _implementation, bytes memory _data) UUPSProxy(_implementation, _data) {} +} + contract DisputeTemplateRegistryProxy is UUPSProxy { constructor(address _implementation, bytes memory _data) UUPSProxy(_implementation, _data) {} } From 268291715c09cde786f8532e7d34073470e00c8e Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 13 Jan 2025 18:57:48 +0000 Subject: [PATCH 05/42] fix: ensure that getDispute() includes the answer ID 0 --- kleros-sdk/src/utils/getDispute.ts | 13 ++ kleros-sdk/test/getDispute.test.ts | 222 +++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 kleros-sdk/test/getDispute.test.ts diff --git a/kleros-sdk/src/utils/getDispute.ts b/kleros-sdk/src/utils/getDispute.ts index c73fa1f24..4631e005c 100644 --- a/kleros-sdk/src/utils/getDispute.ts +++ b/kleros-sdk/src/utils/getDispute.ts @@ -56,5 +56,18 @@ export const getDispute = async (disputeParameters: GetDisputeParameters): Promi const populatedTemplate = populateTemplate(templateData, data); + // Ensure Refuse to Arbitrate option exists + if (!populatedTemplate.answers?.some((answer) => answer.id && Number(answer.id) === 0)) { + populatedTemplate.answers = [ + { + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }, + ...(populatedTemplate.answers || []), + ]; + } + return populatedTemplate; }; diff --git a/kleros-sdk/test/getDispute.test.ts b/kleros-sdk/test/getDispute.test.ts new file mode 100644 index 000000000..555b2a4d9 --- /dev/null +++ b/kleros-sdk/test/getDispute.test.ts @@ -0,0 +1,222 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { getDispute } from "../src/utils/getDispute"; +import fetchDisputeDetails from "../src/requests/fetchDisputeDetails"; +import fetchDisputeTemplateFromId from "../src/requests/fetchDisputeTemplateFromId"; + +// Mock the dependencies +vi.mock("../src/requests/fetchDisputeDetails"); +vi.mock("../src/requests/fetchDisputeTemplateFromId"); + +describe("getDispute", () => { + const mockDisputeId = 123n; + const mockCoreSubgraph = "https://api.thegraph.com/subgraphs/name/kleros/core"; + const mockDtrSubgraph = "https://api.thegraph.com/subgraphs/name/kleros/dtr"; + + const mockDisputeDetails = { + dispute: { + templateId: 1, + arbitrated: { id: "0x1234" }, + arbitrableChainId: 1, + externalDisputeId: 123, + }, + }; + + beforeEach(() => { + vi.resetAllMocks(); + }); + + it("should add Refuse to Arbitrate option when answers array is empty", async () => { + const mockTemplate = { + disputeTemplate: { + templateData: JSON.stringify({ + title: "Test Dispute", + description: "Test Description", + question: "Test Question", + answers: [], + policyURI: "/ipfs/test", + arbitratorChainID: "1", + arbitratorAddress: "0x1234567890123456789012345678901234567890", + version: "1.0.0", + }), + templateDataMappings: "", + }, + }; + + vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate); + + const result = await getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }); + + expect(result?.answers).toHaveLength(1); + expect(result?.answers[0]).toEqual({ + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }); + }); + + it("should add Refuse to Arbitrate option when it doesn't exist in answers", async () => { + const mockTemplate = { + disputeTemplate: { + templateData: JSON.stringify({ + title: "Test Dispute", + description: "Test Description", + question: "Test Question", + answers: [ + { + id: "0x1", + title: "Yes", + description: "Yes Description", + }, + { + id: "0x2", + title: "No", + description: "No Description", + }, + ], + policyURI: "/ipfs/test", + arbitratorChainID: "1", + arbitratorAddress: "0x1234567890123456789012345678901234567890", + version: "1.0.0", + }), + templateDataMappings: "", + }, + }; + + vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate); + + const result = await getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }); + + expect(result?.answers).toHaveLength(3); + expect(result?.answers[0]).toEqual({ + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }); + expect(result?.answers[1].id).toBe("0x1"); + expect(result?.answers[2].id).toBe("0x2"); + }); + + it("should not add Refuse to Arbitrate option when it already exists with id 0x0", async () => { + const mockTemplate = { + disputeTemplate: { + templateData: JSON.stringify({ + title: "Test Dispute", + description: "Test Description", + question: "Test Question", + answers: [ + { + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }, + { + id: "0x1", + title: "Yes", + description: "Yes Description", + }, + ], + policyURI: "/ipfs/test", + arbitratorChainID: "1", + arbitratorAddress: "0x1234567890123456789012345678901234567890", + version: "1.0.0", + }), + templateDataMappings: "", + }, + }; + + vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate); + + const result = await getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }); + + expect(result?.answers).toHaveLength(2); + expect(result?.answers[0].id).toBe("0x0"); + expect(result?.answers[1].id).toBe("0x1"); + }); + + it("should not add Refuse to Arbitrate option when it already exists with id 0x00", async () => { + const mockTemplate = { + disputeTemplate: { + templateData: JSON.stringify({ + title: "Test Dispute", + description: "Test Description", + question: "Test Question", + answers: [ + { + id: "0x00", + title: "Custom Refuse Title", + description: "Custom Refuse Description", + reserved: true, + }, + { + id: "0x1", + title: "Yes", + description: "Yes Description", + }, + ], + policyURI: "/ipfs/test", + arbitratorChainID: "1", + arbitratorAddress: "0x1234567890123456789012345678901234567890", + version: "1.0.0", + }), + templateDataMappings: "", + }, + }; + + vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate); + + const result = await getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }); + + expect(result?.answers).toHaveLength(2); + expect(result?.answers[0].id).toBe("0x00"); + expect(result?.answers[0].title).toBe("Custom Refuse Title"); + expect(result?.answers[1].id).toBe("0x1"); + }); + + it("should throw NotFoundError when dispute details are not found", async () => { + vi.mocked(fetchDisputeDetails).mockResolvedValue({ dispute: null } as any); + + await expect( + getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }) + ).rejects.toThrow("Dispute details not found"); + }); + + it("should throw NotFoundError when template is not found", async () => { + vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(undefined); + + await expect( + getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }) + ).rejects.toThrow("Template not found"); + }); +}); From 6d0cbebc4d254be7730662df991ed2be345cd7d9 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 13 Jan 2025 18:58:39 +0000 Subject: [PATCH 06/42] chore(sdk): release @kleros/kleros-sdk@2.1.11 --- kleros-sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kleros-sdk/package.json b/kleros-sdk/package.json index 8b29618ac..3b5b1c25c 100644 --- a/kleros-sdk/package.json +++ b/kleros-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@kleros/kleros-sdk", - "version": "2.1.10", + "version": "2.1.11", "description": "SDK for Kleros version 2", "repository": "git@github.com:kleros/kleros-v2.git", "homepage": "https://github.com/kleros/kleros-v2/tree/master/kleros-sdk#readme", From 2c9ea339b8a10d8dd2eecbee5adcab8c40e7aa70 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 14 Jan 2025 17:39:35 +0000 Subject: [PATCH 07/42] fix: unconditionally add the standard answer with id 0 --- kleros-sdk/src/utils/getDispute.ts | 22 ++++++-------- kleros-sdk/test/getDispute.test.ts | 49 ++++++++++++++---------------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/kleros-sdk/src/utils/getDispute.ts b/kleros-sdk/src/utils/getDispute.ts index 4631e005c..ca7a500fe 100644 --- a/kleros-sdk/src/utils/getDispute.ts +++ b/kleros-sdk/src/utils/getDispute.ts @@ -56,18 +56,16 @@ export const getDispute = async (disputeParameters: GetDisputeParameters): Promi const populatedTemplate = populateTemplate(templateData, data); - // Ensure Refuse to Arbitrate option exists - if (!populatedTemplate.answers?.some((answer) => answer.id && Number(answer.id) === 0)) { - populatedTemplate.answers = [ - { - id: "0x0", - title: "Refuse to Arbitrate / Invalid", - description: "Refuse to Arbitrate / Invalid", - reserved: true, - }, - ...(populatedTemplate.answers || []), - ]; - } + // Filter out any existing answer with id 0 and add our standard Refuse to Arbitrate option + populatedTemplate.answers = [ + { + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }, + ...(populatedTemplate.answers?.filter((answer) => answer.id && Number(answer.id) !== 0) || []), + ]; return populatedTemplate; }; diff --git a/kleros-sdk/test/getDispute.test.ts b/kleros-sdk/test/getDispute.test.ts index 555b2a4d9..0d1fadc0f 100644 --- a/kleros-sdk/test/getDispute.test.ts +++ b/kleros-sdk/test/getDispute.test.ts @@ -12,6 +12,13 @@ describe("getDispute", () => { const mockCoreSubgraph = "https://api.thegraph.com/subgraphs/name/kleros/core"; const mockDtrSubgraph = "https://api.thegraph.com/subgraphs/name/kleros/dtr"; + const standardRefuseToArbitrateAnswer = { + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }; + const mockDisputeDetails = { dispute: { templateId: 1, @@ -52,12 +59,7 @@ describe("getDispute", () => { }); expect(result?.answers).toHaveLength(1); - expect(result?.answers[0]).toEqual({ - id: "0x0", - title: "Refuse to Arbitrate / Invalid", - description: "Refuse to Arbitrate / Invalid", - reserved: true, - }); + expect(result?.answers[0]).toEqual(standardRefuseToArbitrateAnswer); }); it("should add Refuse to Arbitrate option when it doesn't exist in answers", async () => { @@ -98,18 +100,14 @@ describe("getDispute", () => { }); expect(result?.answers).toHaveLength(3); - expect(result?.answers[0]).toEqual({ - id: "0x0", - title: "Refuse to Arbitrate / Invalid", - description: "Refuse to Arbitrate / Invalid", - reserved: true, - }); + expect(result?.answers[0]).toEqual(standardRefuseToArbitrateAnswer); expect(result?.answers[1].id).toBe("0x1"); expect(result?.answers[2].id).toBe("0x2"); }); - it("should not add Refuse to Arbitrate option when it already exists with id 0x0", async () => { - const mockTemplate = { + it("should overwrite existing answer with id 0x0 or 0x00", async () => { + // Test with 0x0 + const mockTemplate0x0 = { disputeTemplate: { templateData: JSON.stringify({ title: "Test Dispute", @@ -118,8 +116,8 @@ describe("getDispute", () => { answers: [ { id: "0x0", - title: "Refuse to Arbitrate / Invalid", - description: "Refuse to Arbitrate / Invalid", + title: "Custom Refuse Title", + description: "Custom Refuse Description", reserved: true, }, { @@ -138,21 +136,20 @@ describe("getDispute", () => { }; vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); - vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate0x0); - const result = await getDispute({ + let result = await getDispute({ disputeId: mockDisputeId, coreSubgraph: mockCoreSubgraph, dtrSubgraph: mockDtrSubgraph, }); expect(result?.answers).toHaveLength(2); - expect(result?.answers[0].id).toBe("0x0"); + expect(result?.answers[0]).toEqual(standardRefuseToArbitrateAnswer); expect(result?.answers[1].id).toBe("0x1"); - }); - it("should not add Refuse to Arbitrate option when it already exists with id 0x00", async () => { - const mockTemplate = { + // Test with 0x00 + const mockTemplate0x00 = { disputeTemplate: { templateData: JSON.stringify({ title: "Test Dispute", @@ -180,18 +177,16 @@ describe("getDispute", () => { }, }; - vi.mocked(fetchDisputeDetails).mockResolvedValue(mockDisputeDetails); - vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate); + vi.mocked(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate0x00); - const result = await getDispute({ + result = await getDispute({ disputeId: mockDisputeId, coreSubgraph: mockCoreSubgraph, dtrSubgraph: mockDtrSubgraph, }); expect(result?.answers).toHaveLength(2); - expect(result?.answers[0].id).toBe("0x00"); - expect(result?.answers[0].title).toBe("Custom Refuse Title"); + expect(result?.answers[0]).toEqual(standardRefuseToArbitrateAnswer); expect(result?.answers[1].id).toBe("0x1"); }); From 147215a78772b5dfd11418446cad29f3e8e4b25f Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 14 Jan 2025 17:39:56 +0000 Subject: [PATCH 08/42] chore(sdk): release @kleros/kleros-sdk@2.1.12 --- kleros-sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kleros-sdk/package.json b/kleros-sdk/package.json index 3b5b1c25c..25f8014b4 100644 --- a/kleros-sdk/package.json +++ b/kleros-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@kleros/kleros-sdk", - "version": "2.1.11", + "version": "2.1.12", "description": "SDK for Kleros version 2", "repository": "git@github.com:kleros/kleros-v2.git", "homepage": "https://github.com/kleros/kleros-v2/tree/master/kleros-sdk#readme", From 763546c44f9e0575c932042485dc421a77ed73f8 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:44:30 +0100 Subject: [PATCH 09/42] chore: footer icons alignment --- web/src/assets/svgs/socialmedia/discord.svg | 8 ++++---- web/src/assets/svgs/socialmedia/github.svg | 8 ++++---- web/src/assets/svgs/socialmedia/linkedin.svg | 8 ++++---- web/src/assets/svgs/socialmedia/telegram.svg | 8 ++++---- web/src/assets/svgs/socialmedia/x.svg | 2 +- web/src/assets/svgs/socialmedia/youtube.svg | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/web/src/assets/svgs/socialmedia/discord.svg b/web/src/assets/svgs/socialmedia/discord.svg index 8689eefd8..4bc779005 100644 --- a/web/src/assets/svgs/socialmedia/discord.svg +++ b/web/src/assets/svgs/socialmedia/discord.svg @@ -1,10 +1,10 @@ - - + + - - + + diff --git a/web/src/assets/svgs/socialmedia/github.svg b/web/src/assets/svgs/socialmedia/github.svg index 74c5a4a9c..96a495933 100644 --- a/web/src/assets/svgs/socialmedia/github.svg +++ b/web/src/assets/svgs/socialmedia/github.svg @@ -1,10 +1,10 @@ - - + + - - + + diff --git a/web/src/assets/svgs/socialmedia/linkedin.svg b/web/src/assets/svgs/socialmedia/linkedin.svg index 0b10e33e9..5e82fa6ca 100644 --- a/web/src/assets/svgs/socialmedia/linkedin.svg +++ b/web/src/assets/svgs/socialmedia/linkedin.svg @@ -1,10 +1,10 @@ - - + + - - + + diff --git a/web/src/assets/svgs/socialmedia/telegram.svg b/web/src/assets/svgs/socialmedia/telegram.svg index 85690c4fd..73c7f5098 100644 --- a/web/src/assets/svgs/socialmedia/telegram.svg +++ b/web/src/assets/svgs/socialmedia/telegram.svg @@ -1,10 +1,10 @@ - - + + - - + + diff --git a/web/src/assets/svgs/socialmedia/x.svg b/web/src/assets/svgs/socialmedia/x.svg index e9faf4183..0643bf9cd 100644 --- a/web/src/assets/svgs/socialmedia/x.svg +++ b/web/src/assets/svgs/socialmedia/x.svg @@ -1,3 +1,3 @@ - + diff --git a/web/src/assets/svgs/socialmedia/youtube.svg b/web/src/assets/svgs/socialmedia/youtube.svg index 6411254ea..f2635fe0c 100644 --- a/web/src/assets/svgs/socialmedia/youtube.svg +++ b/web/src/assets/svgs/socialmedia/youtube.svg @@ -1,9 +1,9 @@ - + - + From 3d298b9c7082d89d16f29de5d89318ad66803128 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Jan 2025 12:41:51 +0000 Subject: [PATCH 10/42] chore: moved sdk config to correct folder --- .../escrow/{ => example1}/DataMappings.escrow.jsonc | 0 .../escrow/{ => example1}/DisputeDetails.escrow.jsonc.mustache | 0 .../example4 => escrow/example2}/DisputeMappings.json.mustache | 0 .../example4 => escrow/example2}/DisputeTemplate.json.mustache | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename kleros-sdk/config/v2-disputetemplate/escrow/{ => example1}/DataMappings.escrow.jsonc (100%) rename kleros-sdk/config/v2-disputetemplate/escrow/{ => example1}/DisputeDetails.escrow.jsonc.mustache (100%) rename kleros-sdk/config/v2-disputetemplate/{reality/example4 => escrow/example2}/DisputeMappings.json.mustache (100%) rename kleros-sdk/config/v2-disputetemplate/{reality/example4 => escrow/example2}/DisputeTemplate.json.mustache (100%) diff --git a/kleros-sdk/config/v2-disputetemplate/escrow/DataMappings.escrow.jsonc b/kleros-sdk/config/v2-disputetemplate/escrow/example1/DataMappings.escrow.jsonc similarity index 100% rename from kleros-sdk/config/v2-disputetemplate/escrow/DataMappings.escrow.jsonc rename to kleros-sdk/config/v2-disputetemplate/escrow/example1/DataMappings.escrow.jsonc diff --git a/kleros-sdk/config/v2-disputetemplate/escrow/DisputeDetails.escrow.jsonc.mustache b/kleros-sdk/config/v2-disputetemplate/escrow/example1/DisputeDetails.escrow.jsonc.mustache similarity index 100% rename from kleros-sdk/config/v2-disputetemplate/escrow/DisputeDetails.escrow.jsonc.mustache rename to kleros-sdk/config/v2-disputetemplate/escrow/example1/DisputeDetails.escrow.jsonc.mustache diff --git a/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeMappings.json.mustache b/kleros-sdk/config/v2-disputetemplate/escrow/example2/DisputeMappings.json.mustache similarity index 100% rename from kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeMappings.json.mustache rename to kleros-sdk/config/v2-disputetemplate/escrow/example2/DisputeMappings.json.mustache diff --git a/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeTemplate.json.mustache b/kleros-sdk/config/v2-disputetemplate/escrow/example2/DisputeTemplate.json.mustache similarity index 100% rename from kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeTemplate.json.mustache rename to kleros-sdk/config/v2-disputetemplate/escrow/example2/DisputeTemplate.json.mustache From 294278a57a14cbad6c2d063b28597ea12b28fcf4 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 18 Dec 2024 17:05:59 +0000 Subject: [PATCH 11/42] feat: gated dispute kit --- .../dispute-kits/DisputeKitGated.sol | 639 ++++++++++++++++++ 1 file changed, 639 insertions(+) create mode 100644 contracts/src/arbitration/dispute-kits/DisputeKitGated.sol diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol new file mode 100644 index 000000000..c33bd0f13 --- /dev/null +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: MIT + +/// @custom:authors: [@unknownunknown1, @jaybuidl] +/// @custom:reviewers: [] +/// @custom:auditors: [] +/// @custom:bounties: [] +/// @custom:deployments: [] + +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +import "../KlerosCore.sol"; +import "../interfaces/IDisputeKit.sol"; +import "../../proxy/UUPSProxiable.sol"; +import "../../proxy/Initializable.sol"; + +interface IToken { + /// @dev Returns the number of tokens in `owner` account. + /// @param owner The address of the owner. + /// @return balance The number of tokens in `owner` account. + function balanceOf(address owner) external view returns (uint256 balance); +} + +/// @title DisputeKitGated +/// Dispute kit implementation adapted from DisputeKitClassic +/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate`, +/// - a vote aggregation system: plurality, +/// - an incentive system: equal split between coherent votes, +/// - an appeal system: fund 2 choices only, vote on any choice. +contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { + // ************************************* // + // * Structs * // + // ************************************* // + + struct Dispute { + Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. + uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". + bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. + mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. + bytes extraData; // Extradata for the dispute. + } + + struct Round { + Vote[] votes; // Former votes[_appeal][]. + uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first. + mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`. + bool tied; // True if there is a tie, false otherwise. + uint256 totalVoted; // Former uint[_appeal] votesInEachRound. + uint256 totalCommitted; // Former commitsInRound. + mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round. + mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise. + mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice. + uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. + uint256[] fundedChoices; // Stores the choices that are fully funded. + uint256 nbVotes; // Maximal number of votes this dispute can get. + } + + struct Vote { + address account; // The address of the juror. + bytes32 commit; // The commit of the juror. For courts with hidden votes. + uint256 choice; // The choice of the juror. + bool voted; // True if the vote has been cast. + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. + uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. + uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. + uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. + + address public governor; // The governor of the contract. + KlerosCore public core; // The Kleros Core arbitrator + Dispute[] public disputes; // Array of the locally created disputes. + mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. + IToken public tokenGate; // The token used for gating access. + + // ************************************* // + // * Events * // + // ************************************* // + + /// @dev To be emitted when a dispute is created. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _numberOfChoices The number of choices available in the dispute. + /// @param _extraData The extra data for the dispute. + event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData); + + /// @dev To be emitted when a vote commitment is cast. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _juror The address of the juror casting the vote commitment. + /// @param _voteIDs The identifiers of the votes in the dispute. + /// @param _commit The commitment of the juror. + event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit); + + /// @dev To be emitted when a funding contribution is made. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + /// @param _contributor The address of the contributor. + /// @param _amount The amount contributed. + event Contribution( + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /// @dev To be emitted when the contributed funds are withdrawn. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + /// @param _contributor The address of the contributor. + /// @param _amount The amount withdrawn. + event Withdrawal( + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /// @dev To be emitted when a choice is fully funded for an appeal. + /// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract. + /// @param _coreRoundID The identifier of the round in the Arbitrator contract. + /// @param _choice The choice that is being funded. + event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); + + // ************************************* // + // * Modifiers * // + // ************************************* // + + modifier onlyByGovernor() { + require(governor == msg.sender, "Access not allowed: Governor only."); + _; + } + + modifier onlyByCore() { + require(address(core) == msg.sender, "Access not allowed: KlerosCore only."); + _; + } + + modifier notJumped(uint256 _coreDisputeID) { + require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + + // ************************************* // + // * Constructor * // + // ************************************* // + + /// @dev Constructor, initializing the implementation to reduce attack surface. + constructor() { + _disableInitializers(); + } + + /// @dev Initializer. + /// @param _governor The governor's address. + /// @param _core The KlerosCore arbitrator. + /// @param _tokenGate The token used for gating access. + function initialize(address _governor, KlerosCore _core, IToken _tokenGate) external reinitializer(1) { + governor = _governor; + core = _core; + tokenGate = _tokenGate; + } + + // ************************ // + // * Governance * // + // ************************ // + + /// @dev Access Control to perform implementation upgrades (UUPS Proxiable) + /// Only the governor can perform upgrades (`onlyByGovernor`) + function _authorizeUpgrade(address) internal view override onlyByGovernor { + // NOP + } + + /// @dev Allows the governor to call anything on behalf of the contract. + /// @param _destination The destination of the call. + /// @param _amount The value sent with the call. + /// @param _data The data sent with the call. + function executeGovernorProposal( + address _destination, + uint256 _amount, + bytes memory _data + ) external onlyByGovernor { + (bool success, ) = _destination.call{value: _amount}(_data); + require(success, "Unsuccessful call"); + } + + /// @dev Changes the `governor` storage variable. + /// @param _governor The new value for the `governor` storage variable. + function changeGovernor(address payable _governor) external onlyByGovernor { + governor = _governor; + } + + /// @dev Changes the `core` storage variable. + /// @param _core The new value for the `core` storage variable. + function changeCore(address _core) external onlyByGovernor { + core = KlerosCore(_core); + } + + /// @dev Changes the `tokenGate` storage variable. + /// @param _tokenGate The new value for the `tokenGate` storage variable. + function changeTokenGate(address _tokenGate) external onlyByGovernor { + tokenGate = IToken(_tokenGate); + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /// @dev Creates a local dispute and maps it to the dispute ID in the Core contract. + /// Note: Access restricted to Kleros Core only. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _numberOfChoices Number of choices of the dispute + /// @param _extraData Additional info about the dispute, for possible use in future dispute kits. + /// @param _nbVotes Number of votes for this dispute. + function createDispute( + uint256 _coreDisputeID, + uint256 _numberOfChoices, + bytes calldata _extraData, + uint256 _nbVotes + ) external override onlyByCore { + uint256 localDisputeID = disputes.length; + Dispute storage dispute = disputes.push(); + dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + + // New round in the Core should be created before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; + + Round storage round = dispute.rounds.push(); + round.nbVotes = _nbVotes; + round.tied = true; + + coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; + emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData); + } + + /// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. + /// Note: Access restricted to Kleros Core only. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _nonce Nonce of the drawing iteration. + /// @return drawnAddress The drawn address. + function draw( + uint256 _coreDisputeID, + uint256 _nonce + ) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + + ISortitionModule sortitionModule = core.sortitionModule(); + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree. + + drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce); + + if (_postDrawCheck(_coreDisputeID, drawnAddress)) { + round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); + } else { + drawnAddress = address(0); + } + } + + /// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the + /// commit period, each call overrides the commits of the previous one. + /// `O(n)` where + /// `n` is the number of votes. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _voteIDs The IDs of the votes. + /// @param _commit The commit. Note that justification string is a part of the commit. + function castCommit( + uint256 _coreDisputeID, + uint256[] calldata _voteIDs, + bytes32 _commit + ) external notJumped(_coreDisputeID) { + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period."); + require(_commit != bytes32(0), "Empty commit."); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + for (uint256 i = 0; i < _voteIDs.length; i++) { + require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); + round.votes[_voteIDs[i]].commit = _commit; + } + round.totalCommitted += _voteIDs.length; + emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit); + } + + /// @dev Sets the caller's choices for the specified votes. + /// `O(n)` where + /// `n` is the number of votes. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @param _voteIDs The IDs of the votes. + /// @param _choice The choice. + /// @param _salt The salt for the commit if the votes were hidden. + /// @param _justification Justification of the choice. + function castVote( + uint256 _coreDisputeID, + uint256[] calldata _voteIDs, + uint256 _choice, + uint256 _salt, + string memory _justification + ) external notJumped(_coreDisputeID) { + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period."); + require(_voteIDs.length > 0, "No voteID provided"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); + + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + (, bool hiddenVotes, , , , , ) = core.courts(courtID); + + // Save the votes. + for (uint256 i = 0; i < _voteIDs.length; i++) { + require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); + require( + !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), + "The commit must match the choice in courts with hidden votes." + ); + require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); + round.votes[_voteIDs[i]].choice = _choice; + round.votes[_voteIDs[i]].voted = true; + } + + round.totalVoted += _voteIDs.length; + + round.counts[_choice] += _voteIDs.length; + if (_choice == round.winningChoice) { + if (round.tied) round.tied = false; + } else { + // Voted for another choice. + if (round.counts[_choice] == round.counts[round.winningChoice]) { + // Tie. + if (!round.tied) round.tied = true; + } else if (round.counts[_choice] > round.counts[round.winningChoice]) { + // New winner. + round.winningChoice = _choice; + round.tied = false; + } + } + emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification); + } + + /// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. + /// Note that the surplus deposit will be reimbursed. + /// @param _coreDisputeID Index of the dispute in Kleros Core. + /// @param _choice A choice that receives funding. + function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); + + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); + require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); + + uint256 multiplier; + (uint256 ruling, , ) = this.currentRuling(_coreDisputeID); + if (ruling == _choice) { + multiplier = WINNER_STAKE_MULTIPLIER; + } else { + require( + block.timestamp - appealPeriodStart < + ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT, + "Appeal period is over for loser" + ); + multiplier = LOSER_STAKE_MULTIPLIER; + } + + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; + + require(!round.hasPaid[_choice], "Appeal fee is already paid."); + uint256 appealCost = core.appealCost(_coreDisputeID); + uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; + + // Take up to the amount necessary to fund the current round at the current costs. + uint256 contribution; + if (totalCost > round.paidFees[_choice]) { + contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. + ? msg.value + : totalCost - round.paidFees[_choice]; + emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); + } + + round.contributions[msg.sender][_choice] += contribution; + round.paidFees[_choice] += contribution; + if (round.paidFees[_choice] >= totalCost) { + round.feeRewards += round.paidFees[_choice]; + round.fundedChoices.push(_choice); + round.hasPaid[_choice] = true; + emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); + } + + if (round.fundedChoices.length > 1) { + // At least two sides are fully funded. + round.feeRewards = round.feeRewards - appealCost; + + if (core.isDisputeKitJumping(_coreDisputeID)) { + // Don't create a new round in case of a jump, and remove local dispute from the flow. + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); + newRound.tied = true; + } + core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); + } + + if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); + } + + /// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. + /// Note that withdrawals are not possible if the core contract is paused. + /// @param _coreDisputeID Index of the dispute in Kleros Core contract. + /// @param _beneficiary The address whose rewards to withdraw. + /// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from. + /// @param _choice The ruling option that the caller wants to withdraw from. + /// @return amount The withdrawn amount. + function withdrawFeesAndRewards( + uint256 _coreDisputeID, + address payable _beneficiary, + uint256 _coreRoundID, + uint256 _choice + ) external returns (uint256 amount) { + (, , , bool isRuled, ) = core.disputes(_coreDisputeID); + require(isRuled, "Dispute should be resolved."); + require(!core.paused(), "Core is paused"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID); + + if (!round.hasPaid[_choice]) { + // Allow to reimburse if funding was unsuccessful for this ruling option. + amount = round.contributions[_beneficiary][_choice]; + } else { + // Funding was successful for this ruling option. + if (_choice == finalRuling) { + // This ruling option is the ultimate winner. + amount = round.paidFees[_choice] > 0 + ? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice] + : 0; + } else if (!round.hasPaid[finalRuling]) { + // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. + amount = + (round.contributions[_beneficiary][_choice] * round.feeRewards) / + (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); + } + } + round.contributions[_beneficiary][_choice] = 0; + + if (amount != 0) { + _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. + emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); + } + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; + return lastRound.fundedChoices; + } + + /// @dev Gets the current ruling of a specified dispute. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return ruling The current ruling. + /// @return tied Whether it's a tie or not. + /// @return overridden Whether the ruling was overridden by appeal funding or not. + function currentRuling( + uint256 _coreDisputeID + ) external view override returns (uint256 ruling, bool tied, bool overridden) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + tied = round.tied; + ruling = tied ? 0 : round.winningChoice; + (, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID); + // Override the final ruling if only one side funded the appeals. + if (period == KlerosCoreBase.Period.execution) { + uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID); + if (fundedChoices.length == 1) { + ruling = fundedChoices[0]; + tied = false; + overridden = true; + } + } + } + + /// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the vote. + /// @return The degree of coherence in basis points. + function getDegreeOfCoherence( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID, + uint256 /* _feePerJuror */, + uint256 /* _pnkAtStakePerJuror */ + ) external view override returns (uint256) { + // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); + + if (vote.voted && (vote.choice == winningChoice || tied)) { + return ONE_BASIS_POINT; + } else { + return 0; + } + } + + /// @dev Gets the number of jurors who are eligible to a reward in this round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @return The number of coherent jurors. + function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID); + + if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { + return 0; + } else if (tied) { + return currentRound.totalVoted; + } else { + return currentRound.counts[winningChoice]; + } + } + + /// @dev Returns true if all of the jurors have cast their commits for the last round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return Whether all of the jurors have cast their commits for the last round. + function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalCommitted == round.votes.length; + } + + /// @dev Returns true if all of the jurors have cast their votes for the last round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core. + /// @return Whether all of the jurors have cast their votes for the last round. + function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalVoted == round.votes.length; + } + + /// @dev Returns true if the specified voter was active in this round. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the voter. + /// @return Whether the voter was active or not. + function isVoteActive( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + return vote.voted; + } + + function getRoundInfo( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _choice + ) + external + view + override + returns ( + uint256 winningChoice, + bool tied, + uint256 totalVoted, + uint256 totalCommited, + uint256 nbVoters, + uint256 choiceCount + ) + { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + return ( + round.winningChoice, + round.tied, + round.totalVoted, + round.totalCommitted, + round.votes.length, + round.counts[_choice] + ); + } + + function getVoteInfo( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + return (vote.account, vote.commit, vote.choice, vote.voted); + } + + // ************************************* // + // * Internal * // + // ************************************* // + + /// @dev Checks that the chosen address satisfies certain conditions for being drawn. + /// @param _coreDisputeID ID of the dispute in the core contract. + /// @param _juror Chosen address. + /// @return Whether the address can be drawn or not. + /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. + /// minStake is checked directly during staking process however it's possible for the juror to get drawn + /// while having < minStake if it is later increased by governance. + /// This issue is expected and harmless since we check for insolvency anyway. + function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) { + (uint96 courtID, , , , ) = core.disputes(_coreDisputeID); + uint256 lockedAmountPerJuror = core + .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) + .pnkAtStakePerJuror; + (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); + if (totalStaked < totalLocked + lockedAmountPerJuror) { + return false; + } else { + return tokenGate.balanceOf(_juror) > 0; + } + } +} From cca96fc71bbbd47f47ab3656a7672cd8d0ac54b9 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Jan 2025 12:59:11 +0000 Subject: [PATCH 12/42] feat: added support for ERC1155 --- .../dispute-kits/DisputeKitGated.sol | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index 92b5e3e09..190c4ff77 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -12,16 +12,24 @@ import "./DisputeKitClassicBase.sol"; import "../KlerosCore.sol"; import "../../proxy/UUPSProxiable.sol"; -interface IToken { +interface IERC20OrERC721 { /// @dev Returns the number of tokens in `owner` account. /// @param owner The address of the owner. /// @return balance The number of tokens in `owner` account. function balanceOf(address owner) external view returns (uint256 balance); } +interface IERC1155 { + /// @dev Returns the balance of an ERC-1155 token. + /// @param account The address of the token holder + /// @param id ID of the token + /// @return The token balance + function balanceOf(address account, uint256 id) external view returns (uint256); +} + /// @title DisputeKitGated /// Dispute kit implementation adapted from DisputeKitClassic -/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate`, +/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate` where `tokenGate` is an ERC20, ERC721 or ERC1155 /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. @@ -30,7 +38,9 @@ contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { // * Storage * // // ************************************* // - IToken public tokenGate; // The token used for gating access. + address public tokenGate; // The token used for gating access. + uint256 public tokenId; // Only used for ERC-1155 + bool public isERC1155; // True if the tokenGate is an ERC-1155, false otherwise. // ************************************* // // * Constructor * // @@ -45,9 +55,19 @@ contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { /// @param _governor The governor's address. /// @param _core The KlerosCore arbitrator. /// @param _tokenGate The token used for gating access. - function initialize(address _governor, KlerosCore _core, IToken _tokenGate) external reinitializer(1) { + /// @param _tokenId The token ID for ERC-1155 (ignored for other token types) + /// @param _isERC1155 Whether the token is an ERC-1155 + function initialize( + address _governor, + KlerosCore _core, + address _tokenGate, + uint256 _tokenId, + bool _isERC1155 + ) external reinitializer(1) { __DisputeKitClassicBase_initialize(_governor, _core); tokenGate = _tokenGate; + tokenId = _tokenId; + isERC1155 = _isERC1155; } // ************************ // @@ -60,6 +80,22 @@ contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { // NOP } + /// @dev Changes the `tokenGate` to an ERC-20 or ERC-721 token. + /// @param _tokenGate The new value for the `tokenGate` storage variable. + function changeTokenGateERC20OrERC721(address _tokenGate) external onlyByGovernor { + tokenGate = _tokenGate; + isERC1155 = false; + } + + /// @dev Changes the `tokenGate` to an ERC-1155 token. + /// @param _tokenGate The new value for the `tokenGate` storage variable. + /// @param _tokenId The new value for the `tokenId` storage variable. + function changeTokenGateERC1155(address _tokenGate, uint256 _tokenId) external onlyByGovernor { + tokenGate = _tokenGate; + tokenId = _tokenId; + isERC1155 = true; + } + // ************************************* // // * Internal * // // ************************************* // @@ -78,6 +114,12 @@ contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { uint256 _coreDisputeID, address _juror ) internal view override returns (bool) { - return super._postDrawCheck(_round, _coreDisputeID, _juror) && tokenGate.balanceOf(_juror) > 0; + if (!super._postDrawCheck(_round, _coreDisputeID, _juror)) return false; + + if (isERC1155) { + return IERC1155(tokenGate).balanceOf(_juror, tokenId) > 0; + } else { + return IERC20OrERC721(tokenGate).balanceOf(_juror) > 0; + } } } From b38dd0c825285b194d50fbea5c75fd2a44062c2d Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Jan 2025 13:37:31 +0000 Subject: [PATCH 13/42] docs: inherited natspec --- .../src/arbitration/dispute-kits/DisputeKitGated.sol | 10 +--------- .../dispute-kits/DisputeKitSybilResistant.sol | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index 190c4ff77..67ada4d9c 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -100,15 +100,7 @@ contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { // * Internal * // // ************************************* // - /// @dev Checks that the chosen address satisfies certain conditions for being drawn. - /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. - /// minStake is checked directly during staking process however it's possible for the juror to get drawn - /// while having < minStake if it is later increased by governance. - /// This issue is expected and harmless since we check for insolvency anyway. - /// @param _round The round in which the juror is being drawn. - /// @param _coreDisputeID ID of the dispute in the core contract. - /// @param _juror Chosen address. - /// @return result Whether the address passes the check or not. + /// @inheritdoc DisputeKitClassicBase function _postDrawCheck( Round storage _round, uint256 _coreDisputeID, diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index dc076acc3..a305a4439 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -65,15 +65,7 @@ contract DisputeKitSybilResistant is DisputeKitClassicBase, UUPSProxiable { // * Internal * // // ************************************* // - /// @dev Checks that the chosen address satisfies certain conditions for being drawn. - /// Note that we don't check the minStake requirement here because of the implicit staking in parent courts. - /// minStake is checked directly during staking process however it's possible for the juror to get drawn - /// while having < minStake if it is later increased by governance. - /// This issue is expected and harmless since we check for insolvency anyway. - /// @param _round The round in which the juror is being drawn. - /// @param _coreDisputeID ID of the dispute in the core contract. - /// @param _juror Chosen address. - /// @return result Whether the address can be drawn or not. + /// @inheritdoc DisputeKitClassicBase function _postDrawCheck( Round storage _round, uint256 _coreDisputeID, From 3463bb2f7993f59e52411431213ab3fff5c1091d Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Jan 2025 16:22:52 +0000 Subject: [PATCH 14/42] refactor: moved UUPSProxiable to the base contracts --- .../deploy/00-home-chain-arbitration-neo.ts | 2 +- contracts/deploy/upgrade-dispute-kit.ts | 34 +++++++++++++++++++ contracts/deploy/upgrade-kleros-core.ts | 31 +++++------------ contracts/deploy/upgrade-sortition-module.ts | 23 ++++--------- contracts/hardhat.config.ts | 6 ++-- contracts/src/arbitration/KlerosCore.sol | 5 ++- contracts/src/arbitration/KlerosCoreBase.sol | 3 +- contracts/src/arbitration/KlerosCoreNeo.sol | 5 ++- contracts/src/arbitration/SortitionModule.sol | 5 ++- .../src/arbitration/SortitionModuleBase.sol | 13 +++---- .../src/arbitration/SortitionModuleNeo.sol | 5 ++- .../dispute-kits/DisputeKitClassic.sol | 6 ++-- .../dispute-kits/DisputeKitClassicBase.sol | 8 ++--- .../dispute-kits/DisputeKitGated.sol | 6 ++-- .../dispute-kits/DisputeKitSybilResistant.sol | 6 ++-- 15 files changed, 80 insertions(+), 78 deletions(-) create mode 100644 contracts/deploy/upgrade-dispute-kit.ts diff --git a/contracts/deploy/00-home-chain-arbitration-neo.ts b/contracts/deploy/00-home-chain-arbitration-neo.ts index bdddaa999..3aa5ceac9 100644 --- a/contracts/deploy/00-home-chain-arbitration-neo.ts +++ b/contracts/deploy/00-home-chain-arbitration-neo.ts @@ -73,7 +73,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) deployer, deployer, pnk.target, - ZeroAddress, // KlerosCore is configured later + ZeroAddress, // jurorProsecutionModule is not implemented yet disputeKit.address, false, [minStake, alpha, feeForJuror, jurorsForCourtJump], diff --git a/contracts/deploy/upgrade-dispute-kit.ts b/contracts/deploy/upgrade-dispute-kit.ts new file mode 100644 index 000000000..39c168acc --- /dev/null +++ b/contracts/deploy/upgrade-dispute-kit.ts @@ -0,0 +1,34 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { deployUpgradable } from "./utils/deployUpgradable"; +import { HomeChains, isSkipped } from "./utils"; + +const deployUpgradeDisputeKit: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments, getNamedAccounts, getChainId } = hre; + + // fallback to hardhat node signers on local network + const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; + const chainId = Number(await getChainId()); + console.log("upgrading on %s with deployer %s", HomeChains[chainId], deployer); + + try { + console.log("upgrading DisputeKitClassicNeo..."); + await deployUpgradable(deployments, "DisputeKitClassicNeo", { + newImplementation: "DisputeKitGated", + initializer: "initialize", + from: deployer, + // Warning: do not reinitialize everything, only the new variables + args: [], + }); + } catch (err) { + console.error(err); + throw err; + } +}; + +deployUpgradeDisputeKit.tags = ["Upgrade", "DisputeKit"]; +deployUpgradeDisputeKit.skip = async ({ network }) => { + return isSkipped(network, !HomeChains[network.config.chainId ?? 0]); +}; + +export default deployUpgradeDisputeKit; diff --git a/contracts/deploy/upgrade-kleros-core.ts b/contracts/deploy/upgrade-kleros-core.ts index 93472be91..9f504e6cc 100644 --- a/contracts/deploy/upgrade-kleros-core.ts +++ b/contracts/deploy/upgrade-kleros-core.ts @@ -4,36 +4,21 @@ import { deployUpgradable } from "./utils/deployUpgradable"; import { HomeChains, isSkipped } from "./utils"; const deployUpgradeKlerosCore: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { ethers, deployments, getNamedAccounts, getChainId } = hre; - const { ZeroAddress } = hre.ethers; + const { deployments, getNamedAccounts, getChainId } = hre; // fallback to hardhat node signers on local network const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; const chainId = Number(await getChainId()); - console.log("upgrading to %s with deployer %s", HomeChains[chainId], deployer); + console.log("upgrading on %s with deployer %s", HomeChains[chainId], deployer); try { - const pnk = await deployments.get("PNK"); - const disputeKit = await deployments.get("DisputeKitClassic"); - const minStake = 2n * 10n ** 20n; - const alpha = 10000; - const feeForJuror = 10n * 17n; - const sortitionModule = await deployments.get("SortitionModule"); - - console.log("upgrading the KlerosCore..."); - await deployUpgradable(deployments, "KlerosCore", { + console.log("upgrading KlerosCoreNeo..."); + await deployUpgradable(deployments, "KlerosCoreNeo", { + newImplementation: "KlerosCoreNeo", + initializer: "initialize", from: deployer, - args: [ - deployer, - pnk, - ZeroAddress, - disputeKit.address, - false, - [minStake, alpha, feeForJuror, 256], // minStake, alpha, feeForJuror, jurorsForCourtJump - [0, 0, 0, 10], // evidencePeriod, commitPeriod, votePeriod, appealPeriod - ethers.toBeHex(5), // Extra data for sortition module will return the default value of K - sortitionModule.address, - ], + // Warning: do not reinitialize everything, only the new variables + args: [], }); } catch (err) { console.error(err); diff --git a/contracts/deploy/upgrade-sortition-module.ts b/contracts/deploy/upgrade-sortition-module.ts index a556d232f..86a277a1b 100644 --- a/contracts/deploy/upgrade-sortition-module.ts +++ b/contracts/deploy/upgrade-sortition-module.ts @@ -5,29 +5,20 @@ import { HomeChains, isSkipped } from "./utils"; const deployUpgradeSortitionModule: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployments, getNamedAccounts, getChainId } = hre; - const RNG_LOOKAHEAD = 20; // fallback to hardhat node signers on local network const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; const chainId = Number(await getChainId()); - console.log("upgrading to %s with deployer %s", HomeChains[chainId], deployer); + console.log("upgrading on %s with deployer %s", HomeChains[chainId], deployer); try { - const rng = await deployments.get("RandomizerRNG"); - const klerosCore = await deployments.get("KlerosCore"); - const klerosCoreAddress = klerosCore.address; - - console.log("upgrading the SortitionModule..."); - await deployUpgradable(deployments, "SortitionModule", { + console.log("upgrading SortitionModuleNeo..."); + await deployUpgradable(deployments, "SortitionModuleNeo", { + newImplementation: "SortitionModuleNeo", + initializer: "initialize", from: deployer, - args: [ - deployer, - klerosCoreAddress, - 1800, // minStakingTime - 1800, // maxFreezingTime - rng.address, - RNG_LOOKAHEAD, - ], + // Warning: do not reinitialize everything, only the new variables + args: [], }); } catch (err) { console.error(err); diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index ab81d6e95..05b505232 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -95,7 +95,7 @@ const config: HardhatUserConfig = { // Home chain --------------------------------------------------------------------------------- arbitrumSepolia: { chainId: 421614, - url: process.env.ARBITRUM_SEPOLIA_RPC ?? "https://sepolia-rollup.arbitrum.io/rpc", + url: process.env.ARBITRUM_SEPOLIA_RPC ?? `https://arbitrum-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`, accounts: (process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 && [ process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 as string, @@ -121,7 +121,7 @@ const config: HardhatUserConfig = { }, arbitrumSepoliaDevnet: { chainId: 421614, - url: process.env.ARBITRUM_SEPOLIA_RPC ?? "https://sepolia-rollup.arbitrum.io/rpc", + url: process.env.ARBITRUM_SEPOLIA_RPC ?? `https://arbitrum-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`, accounts: (process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 && [ process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 as string, @@ -147,7 +147,7 @@ const config: HardhatUserConfig = { }, arbitrum: { chainId: 42161, - url: "https://arb1.arbitrum.io/rpc", + url: process.env.ARBITRUM_RPC ?? `https://arbitrum-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], live: true, saveDeployments: true, diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 16b901485..30f30e889 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -8,13 +8,12 @@ pragma solidity 0.8.24; -import "./KlerosCoreBase.sol"; -import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; +import {KlerosCoreBase, IDisputeKit, ISortitionModule, IERC20} from "./KlerosCoreBase.sol"; /// @title KlerosCore /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. -contract KlerosCore is KlerosCoreBase, UUPSProxiable { +contract KlerosCore is KlerosCoreBase { // ************************************* // // * Constructor * // // ************************************* // diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index 604ee1252..85541c5fa 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -12,13 +12,14 @@ import {IArbitrableV2, IArbitratorV2} from "./interfaces/IArbitratorV2.sol"; import {IDisputeKit} from "./interfaces/IDisputeKit.sol"; import {ISortitionModule} from "./interfaces/ISortitionModule.sol"; import {Initializable} from "../proxy/Initializable.sol"; +import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; import {SafeERC20, IERC20} from "../libraries/SafeERC20.sol"; import "../libraries/Constants.sol"; /// @title KlerosCoreBase /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. -abstract contract KlerosCoreBase is IArbitratorV2, Initializable { +abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable { using SafeERC20 for IERC20; // ************************************* // diff --git a/contracts/src/arbitration/KlerosCoreNeo.sol b/contracts/src/arbitration/KlerosCoreNeo.sol index 32cacdb79..1e1462224 100644 --- a/contracts/src/arbitration/KlerosCoreNeo.sol +++ b/contracts/src/arbitration/KlerosCoreNeo.sol @@ -8,14 +8,13 @@ pragma solidity 0.8.24; -import "./KlerosCoreBase.sol"; -import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; +import {KlerosCoreBase, IDisputeKit, ISortitionModule, IERC20, OnError, StakingResult} from "./KlerosCoreBase.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @title KlerosCoreNeo /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. -contract KlerosCoreNeo is KlerosCoreBase, UUPSProxiable { +contract KlerosCoreNeo is KlerosCoreBase { // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/SortitionModule.sol b/contracts/src/arbitration/SortitionModule.sol index 09ae0c2bb..36cdfb6ac 100644 --- a/contracts/src/arbitration/SortitionModule.sol +++ b/contracts/src/arbitration/SortitionModule.sol @@ -10,12 +10,11 @@ pragma solidity 0.8.24; -import "./SortitionModuleBase.sol"; -import "../proxy/UUPSProxiable.sol"; +import {SortitionModuleBase, KlerosCore, RNG} from "./SortitionModuleBase.sol"; /// @title SortitionModule /// @dev A factory of trees that keeps track of staked values for sortition. -contract SortitionModule is SortitionModuleBase, UUPSProxiable { +contract SortitionModule is SortitionModuleBase { // ************************************* // // * Constructor * // // ************************************* // diff --git a/contracts/src/arbitration/SortitionModuleBase.sol b/contracts/src/arbitration/SortitionModuleBase.sol index 09ec0733c..2d6e3a9d8 100644 --- a/contracts/src/arbitration/SortitionModuleBase.sol +++ b/contracts/src/arbitration/SortitionModuleBase.sol @@ -10,16 +10,17 @@ pragma solidity 0.8.24; -import "./KlerosCore.sol"; -import "./interfaces/ISortitionModule.sol"; -import "./interfaces/IDisputeKit.sol"; -import "../proxy/Initializable.sol"; -import "../rng/RNG.sol"; +import {KlerosCore} from "./KlerosCore.sol"; +import {ISortitionModule} from "./interfaces/ISortitionModule.sol"; +import {IDisputeKit} from "./interfaces/IDisputeKit.sol"; +import {Initializable} from "../proxy/Initializable.sol"; +import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; +import {RNG} from "../rng/RNG.sol"; import "../libraries/Constants.sol"; /// @title SortitionModuleBase /// @dev A factory of trees that keeps track of staked values for sortition. -abstract contract SortitionModuleBase is ISortitionModule, Initializable { +abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSProxiable { // ************************************* // // * Enums / Structs * // // ************************************* // diff --git a/contracts/src/arbitration/SortitionModuleNeo.sol b/contracts/src/arbitration/SortitionModuleNeo.sol index 727708d85..a63d11ff7 100644 --- a/contracts/src/arbitration/SortitionModuleNeo.sol +++ b/contracts/src/arbitration/SortitionModuleNeo.sol @@ -10,12 +10,11 @@ pragma solidity 0.8.24; -import "./SortitionModuleBase.sol"; -import "../proxy/UUPSProxiable.sol"; +import {SortitionModuleBase, KlerosCore, RNG, StakingResult} from "./SortitionModuleBase.sol"; /// @title SortitionModuleNeo /// @dev A factory of trees that keeps track of staked values for sortition. -contract SortitionModuleNeo is SortitionModuleBase, UUPSProxiable { +contract SortitionModuleNeo is SortitionModuleBase { // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 676fb8806..dff11f54a 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -8,9 +8,7 @@ pragma solidity 0.8.24; -import "./DisputeKitClassicBase.sol"; -import "../KlerosCore.sol"; -import "../../proxy/UUPSProxiable.sol"; +import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; /// @title DisputeKitClassic /// Dispute kit implementation of the Kleros v1 features including: @@ -18,7 +16,7 @@ import "../../proxy/UUPSProxiable.sol"; /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -contract DisputeKitClassic is DisputeKitClassicBase, UUPSProxiable { +contract DisputeKitClassic is DisputeKitClassicBase { // ************************************* // // * Constructor * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index b59e8dcb4..b6865d9f7 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -8,9 +8,9 @@ pragma solidity 0.8.24; -import "../KlerosCore.sol"; -import "../interfaces/IDisputeKit.sol"; -import "../../proxy/Initializable.sol"; +import {KlerosCore, KlerosCoreBase, IDisputeKit, ISortitionModule} from "../KlerosCore.sol"; +import {Initializable} from "../../proxy/Initializable.sol"; +import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol"; /// @title DisputeKitClassicBase /// Abstract Dispute kit classic implementation of the Kleros v1 features including: @@ -18,7 +18,7 @@ import "../../proxy/Initializable.sol"; /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -abstract contract DisputeKitClassicBase is IDisputeKit, Initializable { +abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxiable { // ************************************* // // * Structs * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index 67ada4d9c..4bdcc35a5 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -8,9 +8,7 @@ pragma solidity 0.8.24; -import "./DisputeKitClassicBase.sol"; -import "../KlerosCore.sol"; -import "../../proxy/UUPSProxiable.sol"; +import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; interface IERC20OrERC721 { /// @dev Returns the number of tokens in `owner` account. @@ -33,7 +31,7 @@ interface IERC1155 { /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -contract DisputeKitGated is DisputeKitClassicBase, UUPSProxiable { +contract DisputeKitGated is DisputeKitClassicBase { // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index a305a4439..86febfacb 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -8,9 +8,7 @@ pragma solidity 0.8.24; -import "./DisputeKitClassicBase.sol"; -import "../KlerosCore.sol"; -import "../../proxy/UUPSProxiable.sol"; +import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; interface IProofOfHumanity { /// @dev Return true if the submission is registered and not expired. @@ -25,7 +23,7 @@ interface IProofOfHumanity { /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. -contract DisputeKitSybilResistant is DisputeKitClassicBase, UUPSProxiable { +contract DisputeKitSybilResistant is DisputeKitClassicBase { // ************************************* // // * Storage * // // ************************************* // From 828bb173f73e9b291e5bba4b74ee83e2852f8726 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:32:03 +0100 Subject: [PATCH 15/42] feat: better appeal loading mechanism --- web/src/hooks/useClassicAppealContext.tsx | 11 +++++++++-- .../Appeal/Classic/Options/index.tsx | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/web/src/hooks/useClassicAppealContext.tsx b/web/src/hooks/useClassicAppealContext.tsx index 687fc184c..a0f123a71 100644 --- a/web/src/hooks/useClassicAppealContext.tsx +++ b/web/src/hooks/useClassicAppealContext.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState, createContext, useContext } from "react"; +import React, { useMemo, createContext, useContext, useState } from "react"; import { useParams } from "react-router-dom"; @@ -7,6 +7,7 @@ import { Periods } from "consts/periods"; import { usePopulatedDisputeData } from "hooks/queries/usePopulatedDisputeData"; import { useCountdown } from "hooks/useCountdown"; import { getLocalRounds } from "utils/getLocalRounds"; +import { isUndefined } from "utils/index"; import { useAppealCost } from "queries/useAppealCost"; import { useClassicAppealQuery, ClassicAppealQuery } from "queries/useClassicAppealQuery"; @@ -15,6 +16,7 @@ import { useDisputeKitClassicMultipliers } from "queries/useDisputeKitClassicMul interface ICountdownContext { loserSideCountdown?: number; winnerSideCountdown?: number; + isLoading?: boolean; } const CountdownContext = createContext({}); @@ -73,6 +75,8 @@ export const ClassicAppealProvider: React.FC<{ dispute?.court.timesPerPeriod[Periods.appeal] ); + const isLoading = useMemo(() => isUndefined(dispute) || isUndefined(multipliers), [dispute, multipliers]); + const { loserRequiredFunding, winnerRequiredFunding } = useMemo( () => ({ loserRequiredFunding: getRequiredFunding(appealCost, multipliers?.loser_stake_multiplier), @@ -85,7 +89,10 @@ export const ClassicAppealProvider: React.FC<{ return ( ({ loserSideCountdown, winnerSideCountdown }), [loserSideCountdown, winnerSideCountdown])} + value={useMemo( + () => ({ loserSideCountdown, winnerSideCountdown, isLoading }), + [loserSideCountdown, winnerSideCountdown, isLoading] + )} > ({ selectedOption, setSelectedOption }), [selectedOption, setSelectedOption])} diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx index ceb47eb2a..24c4108d5 100644 --- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx @@ -2,7 +2,6 @@ import React from "react"; import styled from "styled-components"; import { useCountdownContext } from "hooks/useClassicAppealContext"; -import { isUndefined } from "utils/index"; import { StyledSkeleton } from "components/StyledSkeleton"; @@ -18,13 +17,20 @@ interface IOptions { } const Options: React.FC = ({ setAmount }) => { - const { loserSideCountdown } = useCountdownContext(); - return !isUndefined(loserSideCountdown) ? ( + const { loserSideCountdown, isLoading } = useCountdownContext(); + + return ( - {loserSideCountdown > 0 ? : } + {!isLoading ? ( + loserSideCountdown && loserSideCountdown > 0 ? ( + + ) : ( + + ) + ) : ( + + )} - ) : ( - ); }; From e18e65a9f6f58ae7fd968d2401f65df028196375 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:31:10 +0100 Subject: [PATCH 16/42] fix: condition fix --- .../pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx index 24c4108d5..f5416ed1a 100644 --- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/index.tsx @@ -22,7 +22,7 @@ const Options: React.FC = ({ setAmount }) => { return ( {!isLoading ? ( - loserSideCountdown && loserSideCountdown > 0 ? ( + loserSideCountdown > 0 ? ( ) : ( From b4d925a71eb006006eacc56ca072e7f7920485e5 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Jan 2025 19:37:02 +0000 Subject: [PATCH 17/42] feat: versioning of upgradable contracts implementation --- contracts/deploy/upgrade-dispute-kit.ts | 2 +- contracts/src/arbitration/DisputeTemplateRegistry.sol | 2 ++ contracts/src/arbitration/KlerosCore.sol | 2 ++ contracts/src/arbitration/KlerosCoreNeo.sol | 2 ++ contracts/src/arbitration/PolicyRegistry.sol | 2 ++ contracts/src/arbitration/SortitionModule.sol | 2 ++ contracts/src/arbitration/SortitionModuleNeo.sol | 2 ++ contracts/src/arbitration/devtools/KlerosCoreRuler.sol | 2 ++ .../src/arbitration/dispute-kits/DisputeKitClassic.sol | 2 ++ contracts/src/arbitration/dispute-kits/DisputeKitGated.sol | 2 ++ .../arbitration/dispute-kits/DisputeKitSybilResistant.sol | 2 ++ contracts/src/arbitration/evidence/EvidenceModule.sol | 2 ++ .../src/arbitration/university/KlerosCoreUniversity.sol | 2 ++ .../arbitration/university/SortitionModuleUniversity.sol | 2 ++ contracts/src/gateway/ForeignGateway.sol | 2 ++ contracts/src/gateway/HomeGateway.sol | 2 ++ contracts/src/proxy/UUPSProxiable.sol | 6 ++++++ contracts/src/proxy/mock/UUPSUpgradeableMocks.sol | 4 ---- .../src/proxy/mock/by-inheritance/UpgradedByInheritance.sol | 2 +- contracts/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol | 2 +- contracts/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol | 2 +- 21 files changed, 40 insertions(+), 8 deletions(-) diff --git a/contracts/deploy/upgrade-dispute-kit.ts b/contracts/deploy/upgrade-dispute-kit.ts index 39c168acc..f6439c91f 100644 --- a/contracts/deploy/upgrade-dispute-kit.ts +++ b/contracts/deploy/upgrade-dispute-kit.ts @@ -14,7 +14,7 @@ const deployUpgradeDisputeKit: DeployFunction = async (hre: HardhatRuntimeEnviro try { console.log("upgrading DisputeKitClassicNeo..."); await deployUpgradable(deployments, "DisputeKitClassicNeo", { - newImplementation: "DisputeKitGated", + contract: "DisputeKitClassic", initializer: "initialize", from: deployer, // Warning: do not reinitialize everything, only the new variables diff --git a/contracts/src/arbitration/DisputeTemplateRegistry.sol b/contracts/src/arbitration/DisputeTemplateRegistry.sol index bf1b77c6b..e08a861ea 100644 --- a/contracts/src/arbitration/DisputeTemplateRegistry.sol +++ b/contracts/src/arbitration/DisputeTemplateRegistry.sol @@ -8,6 +8,8 @@ import "./interfaces/IDisputeTemplateRegistry.sol"; /// @title Dispute Template Registry /// @dev A contract to maintain a registry of dispute templates. contract DisputeTemplateRegistry is IDisputeTemplateRegistry, UUPSProxiable, Initializable { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 30f30e889..6aa9ceaa8 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -14,6 +14,8 @@ import {KlerosCoreBase, IDisputeKit, ISortitionModule, IERC20} from "./KlerosCor /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. contract KlerosCore is KlerosCoreBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Constructor * // // ************************************* // diff --git a/contracts/src/arbitration/KlerosCoreNeo.sol b/contracts/src/arbitration/KlerosCoreNeo.sol index 1e1462224..4daa6ccf1 100644 --- a/contracts/src/arbitration/KlerosCoreNeo.sol +++ b/contracts/src/arbitration/KlerosCoreNeo.sol @@ -15,6 +15,8 @@ import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// Core arbitrator contract for Kleros v2. /// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts. contract KlerosCoreNeo is KlerosCoreBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/PolicyRegistry.sol b/contracts/src/arbitration/PolicyRegistry.sol index cec29e76c..130cdb9e7 100644 --- a/contracts/src/arbitration/PolicyRegistry.sol +++ b/contracts/src/arbitration/PolicyRegistry.sol @@ -7,6 +7,8 @@ import "../proxy/Initializable.sol"; /// @title PolicyRegistry /// @dev A contract to maintain a policy for each court. contract PolicyRegistry is UUPSProxiable, Initializable { + string public constant override version = "0.8.0"; + // ************************************* // // * Events * // // ************************************* // diff --git a/contracts/src/arbitration/SortitionModule.sol b/contracts/src/arbitration/SortitionModule.sol index 36cdfb6ac..a62d44932 100644 --- a/contracts/src/arbitration/SortitionModule.sol +++ b/contracts/src/arbitration/SortitionModule.sol @@ -15,6 +15,8 @@ import {SortitionModuleBase, KlerosCore, RNG} from "./SortitionModuleBase.sol"; /// @title SortitionModule /// @dev A factory of trees that keeps track of staked values for sortition. contract SortitionModule is SortitionModuleBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Constructor * // // ************************************* // diff --git a/contracts/src/arbitration/SortitionModuleNeo.sol b/contracts/src/arbitration/SortitionModuleNeo.sol index a63d11ff7..069d61070 100644 --- a/contracts/src/arbitration/SortitionModuleNeo.sol +++ b/contracts/src/arbitration/SortitionModuleNeo.sol @@ -15,6 +15,8 @@ import {SortitionModuleBase, KlerosCore, RNG, StakingResult} from "./SortitionMo /// @title SortitionModuleNeo /// @dev A factory of trees that keeps track of staked values for sortition. contract SortitionModuleNeo is SortitionModuleBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/devtools/KlerosCoreRuler.sol b/contracts/src/arbitration/devtools/KlerosCoreRuler.sol index 3c5aebf8b..7d469c13f 100644 --- a/contracts/src/arbitration/devtools/KlerosCoreRuler.sol +++ b/contracts/src/arbitration/devtools/KlerosCoreRuler.sol @@ -13,6 +13,8 @@ import "../../libraries/Constants.sol"; contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable { using SafeERC20 for IERC20; + string public constant override version = "0.8.0"; + // ************************************* // // * Enums / Structs * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index dff11f54a..c4b2194b3 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -17,6 +17,8 @@ import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. contract DisputeKitClassic is DisputeKitClassicBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Constructor * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index 4bdcc35a5..ef49a280c 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -32,6 +32,8 @@ interface IERC1155 { /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. contract DisputeKitGated is DisputeKitClassicBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 86febfacb..8480d7f06 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -24,6 +24,8 @@ interface IProofOfHumanity { /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. contract DisputeKitSybilResistant is DisputeKitClassicBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/evidence/EvidenceModule.sol b/contracts/src/arbitration/evidence/EvidenceModule.sol index 3f7e80c29..b8a9aa05a 100644 --- a/contracts/src/arbitration/evidence/EvidenceModule.sol +++ b/contracts/src/arbitration/evidence/EvidenceModule.sol @@ -16,6 +16,8 @@ import "../../proxy/Initializable.sol"; /// @title Evidence Module contract EvidenceModule is IEvidence, Initializable, UUPSProxiable { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // diff --git a/contracts/src/arbitration/university/KlerosCoreUniversity.sol b/contracts/src/arbitration/university/KlerosCoreUniversity.sol index d3e5be37c..23e2e9afa 100644 --- a/contracts/src/arbitration/university/KlerosCoreUniversity.sol +++ b/contracts/src/arbitration/university/KlerosCoreUniversity.sol @@ -15,6 +15,8 @@ import {Initializable} from "../../proxy/Initializable.sol"; contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { using SafeERC20 for IERC20; + string public constant override version = "0.8.0"; + // ************************************* // // * Enums / Structs * // // ************************************* // diff --git a/contracts/src/arbitration/university/SortitionModuleUniversity.sol b/contracts/src/arbitration/university/SortitionModuleUniversity.sol index a2d3f2c63..206e57305 100644 --- a/contracts/src/arbitration/university/SortitionModuleUniversity.sol +++ b/contracts/src/arbitration/university/SortitionModuleUniversity.sol @@ -20,6 +20,8 @@ import "../../libraries/Constants.sol"; /// @title SortitionModuleUniversity /// @dev An adapted version of the SortitionModule contract for educational purposes. contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, Initializable { + string public constant override version = "0.8.0"; + // ************************************* // // * Enums / Structs * // // ************************************* // diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol index d69889edf..3ea8f7a77 100644 --- a/contracts/src/gateway/ForeignGateway.sol +++ b/contracts/src/gateway/ForeignGateway.sol @@ -16,6 +16,8 @@ import "../libraries/Constants.sol"; /// Foreign Gateway /// Counterpart of `HomeGateway` contract ForeignGateway is IForeignGateway, UUPSProxiable, Initializable { + string public constant override version = "0.8.0"; + // ************************************* // // * Enums / Structs * // // ************************************* // diff --git a/contracts/src/gateway/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index 180b0e878..c208fbbee 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -20,6 +20,8 @@ import "../proxy/Initializable.sol"; contract HomeGateway is IHomeGateway, UUPSProxiable, Initializable { using SafeERC20 for IERC20; + string public constant override version = "0.8.0"; + // ************************************* // // * Enums / Structs * // // ************************************* // diff --git a/contracts/src/proxy/UUPSProxiable.sol b/contracts/src/proxy/UUPSProxiable.sol index 258a7a622..20ad82d5e 100644 --- a/contracts/src/proxy/UUPSProxiable.sol +++ b/contracts/src/proxy/UUPSProxiable.sol @@ -148,6 +148,12 @@ abstract contract UUPSProxiable { return IMPLEMENTATION_SLOT; } + /** + * @notice Returns the version of the contract. + * @return Version string. + */ + function version() external view virtual returns (string memory); + // ************************************* // // * Internal Views * // // ************************************* // diff --git a/contracts/src/proxy/mock/UUPSUpgradeableMocks.sol b/contracts/src/proxy/mock/UUPSUpgradeableMocks.sol index 8133dd5c5..9467f1fe0 100644 --- a/contracts/src/proxy/mock/UUPSUpgradeableMocks.sol +++ b/contracts/src/proxy/mock/UUPSUpgradeableMocks.sol @@ -16,10 +16,6 @@ contract NonUpgradeableMock { function increment() external { _counter++; } - - function version() external pure virtual returns (string memory) { - return "NonUpgradeableMock 0.0.0"; - } } contract UUPSUpgradeableMock is UUPSProxiable, NonUpgradeableMock { diff --git a/contracts/src/proxy/mock/by-inheritance/UpgradedByInheritance.sol b/contracts/src/proxy/mock/by-inheritance/UpgradedByInheritance.sol index 4634138e7..dab7da8d1 100644 --- a/contracts/src/proxy/mock/by-inheritance/UpgradedByInheritance.sol +++ b/contracts/src/proxy/mock/by-inheritance/UpgradedByInheritance.sol @@ -33,7 +33,7 @@ contract UpgradedByInheritanceV1 is UUPSProxiable, Initializable { ++counter; } - function version() external pure virtual returns (string memory) { + function version() external pure virtual override returns (string memory) { return "V1"; } } diff --git a/contracts/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol b/contracts/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol index abf2e53e6..a508bd96c 100644 --- a/contracts/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol +++ b/contracts/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol @@ -36,7 +36,7 @@ contract UpgradedByRewrite is UUPSProxiable, Initializable { ++counter; } - function version() external pure virtual returns (string memory) { + function version() external pure virtual override returns (string memory) { return "V1"; } } diff --git a/contracts/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol b/contracts/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol index 334a29fdf..09cecd47d 100644 --- a/contracts/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol +++ b/contracts/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol @@ -36,7 +36,7 @@ contract UpgradedByRewrite is UUPSProxiable, Initializable { ++counter; } - function version() external pure virtual returns (string memory) { + function version() external pure virtual override returns (string memory) { return "V2"; } } From 15488d96197397c893c6cd236f50bd4c97e7394a Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:33:16 +0100 Subject: [PATCH 18/42] chore: sort disputes by id instead of latest status change --- web/src/hooks/queries/useCasesQuery.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/hooks/queries/useCasesQuery.ts b/web/src/hooks/queries/useCasesQuery.ts index daa6ac6a2..56e6cfe2f 100644 --- a/web/src/hooks/queries/useCasesQuery.ts +++ b/web/src/hooks/queries/useCasesQuery.ts @@ -17,6 +17,7 @@ export type { CasesPageQuery, DisputeDetailsFragment }; export const disputeFragment = graphql(` fragment DisputeDetails on Dispute { id + disputeID arbitrated { id } @@ -34,7 +35,7 @@ export const disputeFragment = graphql(` const casesQueryWhere = graphql(` query CasesPageWhere($skip: Int, $where: Dispute_filter, $orderDirection: OrderDirection, $first: Int) { - disputes(first: $first, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection, where: $where) { + disputes(first: $first, skip: $skip, orderBy: disputeID, orderDirection: $orderDirection, where: $where) { ...DisputeDetails } } @@ -42,7 +43,7 @@ const casesQueryWhere = graphql(` const casesQuery = graphql(` query CasesPage($skip: Int, $orderDirection: OrderDirection, $first: Int) { - disputes(first: $first, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection) { + disputes(first: $first, skip: $skip, orderBy: disputeID, orderDirection: $orderDirection) { ...DisputeDetails } } @@ -51,7 +52,7 @@ const casesQuery = graphql(` const myCasesQuery = graphql(` query MyCases($id: ID!, $skip: Int, $orderDirection: OrderDirection) { user(id: $id) { - disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection) { + disputes(first: 3, skip: $skip, orderBy: disputeID, orderDirection: $orderDirection) { ...DisputeDetails } } @@ -61,7 +62,7 @@ const myCasesQuery = graphql(` const myCasesQueryWhere = graphql(` query myCasesPageWhere($id: ID!, $skip: Int, $where: Dispute_filter, $orderDirection: OrderDirection) { user(id: $id) { - disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection, where: $where) { + disputes(first: 3, skip: $skip, orderBy: disputeID, orderDirection: $orderDirection, where: $where) { ...DisputeDetails } } From 87f9d08f7e3150ff9dec12e4894d11e264a30bb8 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 22 Jan 2025 00:01:37 +0000 Subject: [PATCH 19/42] feat: upgrade proxies validation, natspec reformat for UUPSProxiable and UUPSProxy --- contracts/package.json | 14 +- .../arbitration/DisputeTemplateRegistry.sol | 2 +- contracts/src/arbitration/KlerosCore.sol | 2 +- contracts/src/arbitration/KlerosCoreNeo.sol | 2 +- contracts/src/arbitration/PolicyRegistry.sol | 2 +- contracts/src/arbitration/SortitionModule.sol | 2 +- .../src/arbitration/SortitionModuleNeo.sol | 2 +- .../arbitration/devtools/KlerosCoreRuler.sol | 2 +- .../dispute-kits/DisputeKitClassic.sol | 2 +- .../dispute-kits/DisputeKitGated.sol | 2 +- .../dispute-kits/DisputeKitSybilResistant.sol | 2 +- .../arbitration/evidence/EvidenceModule.sol | 2 +- .../university/KlerosCoreUniversity.sol | 2 +- .../university/SortitionModuleUniversity.sol | 2 +- contracts/src/gateway/ForeignGateway.sol | 2 +- contracts/src/gateway/HomeGateway.sol | 2 +- .../kleros-liquid-xdai/xKlerosLiquidV2.sol | 1 + contracts/src/proxy/UUPSProxiable.sol | 118 +++++-------- contracts/src/proxy/UUPSProxy.sol | 53 ++---- yarn.lock | 167 ++++++++++-------- 20 files changed, 182 insertions(+), 201 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index e2441d609..76d7414c6 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -27,6 +27,7 @@ "start-local": "hardhat node --tags Arbitration,HomeArbitrable --hostname 0.0.0.0", "deploy": "hardhat deploy", "deploy-local": "hardhat deploy --tags Arbitration,HomeArbitrable --network localhost", + "validate-upgrades": "openzeppelin-upgrades-core validate --exclude 'src/proxy/mock/**/*.sol' --exclude 'src/test/**/*.sol' artifacts/build-info", "simulate": "hardhat simulate:all", "simulate-local": "hardhat simulate:all --network localhost", "viem:generate-devnet": "NODE_NO_WARNINGS=1 wagmi generate -c wagmi.config.devnet.ts", @@ -69,6 +70,7 @@ "@nomicfoundation/hardhat-chai-matchers": "^2.0.8", "@nomicfoundation/hardhat-ethers": "^3.0.8", "@nomiclabs/hardhat-solhint": "^4.0.1", + "@openzeppelin/upgrades-core": "^1.41.0", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/chai": "^4.3.20", @@ -80,23 +82,23 @@ "dotenv": "^16.4.5", "eslint": "^9.15.0", "ethereumjs-util": "^7.1.5", - "ethers": "^6.13.4", + "ethers": "^6.13.5", "graphql": "^16.9.0", "graphql-request": "^7.1.2", - "hardhat": "2.22.16", + "hardhat": "2.22.18", "hardhat-contract-sizer": "^2.10.0", "hardhat-deploy": "^0.14.0", "hardhat-deploy-ethers": "^0.4.2", - "hardhat-deploy-tenderly": "^0.2.0", + "hardhat-deploy-tenderly": "^0.2.1", "hardhat-docgen": "^1.3.0", - "hardhat-gas-reporter": "^2.2.1", + "hardhat-gas-reporter": "^2.2.2", "hardhat-tracer": "^3.1.0", "hardhat-watcher": "^2.5.0", "node-fetch": "^3.3.2", "pino": "^8.21.0", "pino-pretty": "^10.3.1", "prettier": "^3.3.3", - "prettier-plugin-solidity": "^1.4.1", + "prettier-plugin-solidity": "^1.4.2", "shelljs": "^0.8.5", "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.13", @@ -107,7 +109,7 @@ "dependencies": { "@chainlink/contracts": "^1.3.0", "@kleros/vea-contracts": "^0.4.0", - "@openzeppelin/contracts": "^5.1.0", + "@openzeppelin/contracts": "^5.2.0", "viem": "^2.21.48" } } diff --git a/contracts/src/arbitration/DisputeTemplateRegistry.sol b/contracts/src/arbitration/DisputeTemplateRegistry.sol index e08a861ea..ca22c6400 100644 --- a/contracts/src/arbitration/DisputeTemplateRegistry.sol +++ b/contracts/src/arbitration/DisputeTemplateRegistry.sol @@ -33,7 +33,7 @@ contract DisputeTemplateRegistry is IDisputeTemplateRegistry, UUPSProxiable, Ini // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 6aa9ceaa8..ceaa6b4e0 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -20,7 +20,7 @@ contract KlerosCore is KlerosCoreBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/KlerosCoreNeo.sol b/contracts/src/arbitration/KlerosCoreNeo.sol index 4daa6ccf1..205a50720 100644 --- a/contracts/src/arbitration/KlerosCoreNeo.sol +++ b/contracts/src/arbitration/KlerosCoreNeo.sol @@ -28,7 +28,7 @@ contract KlerosCoreNeo is KlerosCoreBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/PolicyRegistry.sol b/contracts/src/arbitration/PolicyRegistry.sol index 130cdb9e7..7150ba0f1 100644 --- a/contracts/src/arbitration/PolicyRegistry.sol +++ b/contracts/src/arbitration/PolicyRegistry.sol @@ -40,7 +40,7 @@ contract PolicyRegistry is UUPSProxiable, Initializable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/SortitionModule.sol b/contracts/src/arbitration/SortitionModule.sol index a62d44932..828f674c0 100644 --- a/contracts/src/arbitration/SortitionModule.sol +++ b/contracts/src/arbitration/SortitionModule.sol @@ -21,7 +21,7 @@ contract SortitionModule is SortitionModuleBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/SortitionModuleNeo.sol b/contracts/src/arbitration/SortitionModuleNeo.sol index 069d61070..5d725e277 100644 --- a/contracts/src/arbitration/SortitionModuleNeo.sol +++ b/contracts/src/arbitration/SortitionModuleNeo.sol @@ -29,7 +29,7 @@ contract SortitionModuleNeo is SortitionModuleBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/devtools/KlerosCoreRuler.sol b/contracts/src/arbitration/devtools/KlerosCoreRuler.sol index 7d469c13f..2100ad1c3 100644 --- a/contracts/src/arbitration/devtools/KlerosCoreRuler.sol +++ b/contracts/src/arbitration/devtools/KlerosCoreRuler.sol @@ -166,7 +166,7 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index c4b2194b3..ef4591abd 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -23,7 +23,7 @@ contract DisputeKitClassic is DisputeKitClassicBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index ef49a280c..f4dffc787 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -46,7 +46,7 @@ contract DisputeKitGated is DisputeKitClassicBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 8480d7f06..0c6d98d5e 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -36,7 +36,7 @@ contract DisputeKitSybilResistant is DisputeKitClassicBase { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/evidence/EvidenceModule.sol b/contracts/src/arbitration/evidence/EvidenceModule.sol index b8a9aa05a..21405bc6d 100644 --- a/contracts/src/arbitration/evidence/EvidenceModule.sol +++ b/contracts/src/arbitration/evidence/EvidenceModule.sol @@ -37,7 +37,7 @@ contract EvidenceModule is IEvidence, Initializable, UUPSProxiable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/university/KlerosCoreUniversity.sol b/contracts/src/arbitration/university/KlerosCoreUniversity.sol index 23e2e9afa..1f2e888e2 100644 --- a/contracts/src/arbitration/university/KlerosCoreUniversity.sol +++ b/contracts/src/arbitration/university/KlerosCoreUniversity.sol @@ -182,7 +182,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/arbitration/university/SortitionModuleUniversity.sol b/contracts/src/arbitration/university/SortitionModuleUniversity.sol index 206e57305..7c79651d8 100644 --- a/contracts/src/arbitration/university/SortitionModuleUniversity.sol +++ b/contracts/src/arbitration/university/SortitionModuleUniversity.sol @@ -68,7 +68,7 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable, // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol index 3ea8f7a77..81649b072 100644 --- a/contracts/src/gateway/ForeignGateway.sol +++ b/contracts/src/gateway/ForeignGateway.sol @@ -73,7 +73,7 @@ contract ForeignGateway is IForeignGateway, UUPSProxiable, Initializable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/gateway/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index c208fbbee..ade0de2e6 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -59,7 +59,7 @@ contract HomeGateway is IHomeGateway, UUPSProxiable, Initializable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } diff --git a/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol b/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol index 7bc52279e..17354c3cd 100644 --- a/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol +++ b/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol @@ -16,6 +16,7 @@ import "../../gateway/interfaces/IForeignGateway.sol"; /// @dev This contract is an adaption of Mainnet's KlerosLiquid (https://github.com/kleros/kleros/blob/69cfbfb2128c29f1625b3a99a3183540772fda08/contracts/kleros/KlerosLiquid.sol) /// for xDai chain. Notice that variables referring to ETH values in this contract, will hold the native token values of the chain on which xKlerosLiquid is deployed. /// When this contract gets deployed on xDai chain, ETH variables will hold xDai values. +/// @custom:oz-upgrades-unsafe-allow external-library-linking contract xKlerosLiquidV2 is Initializable, ITokenController, IArbitratorV2 { // ************************************* // // * Enums / Structs * // diff --git a/contracts/src/proxy/UUPSProxiable.sol b/contracts/src/proxy/UUPSProxiable.sol index 20ad82d5e..dc107674b 100644 --- a/contracts/src/proxy/UUPSProxiable.sol +++ b/contracts/src/proxy/UUPSProxiable.sol @@ -1,56 +1,41 @@ //SPDX-License-Identifier: MIT -// Adapted from - -/** - * @authors: [@malatrax] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ + pragma solidity 0.8.24; -/** - * @title UUPS Proxiable - * @author Simon Malatrait - * @dev This contract implements an upgradeability mechanism designed for UUPS proxies. - * The functions included here can perform an upgrade of an UUPS Proxy, when this contract is set as the implementation behind such a proxy. - * - * IMPORTANT: A UUPS proxy requires its upgradeability functions to be in the implementation as opposed to the transparent proxy. - * This means that if the proxy is upgraded to an implementation that does not support this interface, it will no longer be upgradeable. - * - * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is - * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing - * `UUPSProxiable` with a custom implementation of upgrades. - * - * The `_authorizeUpgrade` function must be overridden to include access restriction to the upgrade mechanism. - */ +/// @title UUPS Proxiable +/// @author Simon Malatrait +/// @dev This contract implements an upgradeability mechanism designed for UUPS proxies. +/// @dev Adapted from +/// The functions included here can perform an upgrade of an UUPS Proxy, when this contract is set as the implementation behind such a proxy. +/// +/// IMPORTANT: A UUPS proxy requires its upgradeability functions to be in the implementation as opposed to the transparent proxy. +/// This means that if the proxy is upgraded to an implementation that does not support this interface, it will no longer be upgradeable. +/// +/// A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is +/// reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing +/// `UUPSProxiable` with a custom implementation of upgrades. +/// +/// The `_authorizeUpgrade` function must be overridden to include access restriction to the upgrade mechanism. abstract contract UUPSProxiable { // ************************************* // // * Event * // // ************************************* // - /** - * Emitted when the `implementation` has been successfully upgraded. - * @param newImplementation Address of the new implementation the proxy is now forwarding calls to. - */ + /// @dev Emitted when the `implementation` has been successfully upgraded. + /// @param newImplementation Address of the new implementation the proxy is now forwarding calls to. event Upgraded(address indexed newImplementation); // ************************************* // // * Error * // // ************************************* // - /** - * @dev The call is from an unauthorized context. - */ + /// @dev The call is from an unauthorized context. error UUPSUnauthorizedCallContext(); - /** - * @dev The storage `slot` is unsupported as a UUID. - */ + /// @dev The storage `slot` is unsupported as a UUID. error UUPSUnsupportedProxiableUUID(bytes32 slot); - /// The `implementation` is not UUPS-compliant + /// @dev The `implementation` is not UUPS-compliant error InvalidImplementation(address implementation); /// Failed Delegated call @@ -60,48 +45,40 @@ abstract contract UUPSProxiable { // * Storage * // // ************************************* // - /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is - * validated in the constructor. - * NOTE: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) - */ + /// @dev Storage slot with the address of the current implementation. + /// @dev This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + /// @dev validated in the constructor. + /// @dev NOTE: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - /** - * @dev Storage variable of the proxiable contract address. - * It is used to check whether or not the current call is from the proxy. - */ + /// @dev Storage variable of the proxiable contract address. + /// @dev It is used to check whether or not the current call is from the proxy. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable __self = address(this); // ************************************* // // * Governance * // // ************************************* // - /** - * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. - * @dev Called by {upgradeToAndCall}. - */ + /// @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. + /// @dev Called by {upgradeToAndCall}. function _authorizeUpgrade(address newImplementation) internal virtual; // ************************************* // // * State Modifiers * // // ************************************* // - /** - * @dev Upgrade mechanism including access control and UUPS-compliance. - * @param newImplementation Address of the new implementation contract. - * @param data Data used in a delegate call to `newImplementation` if non-empty. This will typically be an encoded - * function call, and allows initializing the storage of the proxy like a Solidity constructor. - * - * @dev Reverts if the execution is not performed via delegatecall or the execution - * context is not of a proxy with an ERC1967-compliant implementation pointing to self. - */ + /// @dev Upgrade mechanism including access control and UUPS-compliance. + /// @param newImplementation Address of the new implementation contract. + /// @param data Data used in a delegate call to `newImplementation` if non-empty. This will typically be an encoded + /// function call, and allows initializing the storage of the proxy like a Solidity constructor. + /// @dev Reverts if the execution is not performed via delegatecall or the execution + /// context is not of a proxy with an ERC1967-compliant implementation pointing to self. function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual { _authorizeUpgrade(newImplementation); - /* Check that the execution is being performed through a delegatecall call and that the execution context is - a proxy contract with an implementation (as defined in ERC1967) pointing to self. */ + // Check that the execution is being performed through a delegatecall call and that the execution context is + // a proxy contract with an implementation (as defined in ERC1967) pointing to self. if (address(this) == __self || _getImplementation() != __self) { revert UUPSUnauthorizedCallContext(); } @@ -118,6 +95,7 @@ abstract contract UUPSProxiable { if (data.length != 0) { // The return data is not checked (checking, in case of success, that the newImplementation code is non-empty if the return data is empty) because the authorized callee is trusted. + /// @custom:oz-upgrades-unsafe-allow delegatecall (bool success, ) = newImplementation.delegatecall(data); if (!success) { revert FailedDelegateCall(); @@ -132,14 +110,12 @@ abstract contract UUPSProxiable { // * Public Views * // // ************************************* // - /** - * @dev Implementation of the ERC1822 `proxiableUUID` function. This returns the storage slot used by the - * implementation. It is used to validate the implementation's compatibility when performing an upgrade. - * - * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks - * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this - * function revert if invoked through a proxy. This is guaranteed by the if statement. - */ + /// @dev Implementation of the ERC1822 `proxiableUUID` function. This returns the storage slot used by the + /// implementation. It is used to validate the implementation's compatibility when performing an upgrade. + /// + /// IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks + /// bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this + /// function revert if invoked through a proxy. This is guaranteed by the if statement. function proxiableUUID() external view virtual returns (bytes32) { if (address(this) != __self) { // Must not be called through delegatecall @@ -148,10 +124,8 @@ abstract contract UUPSProxiable { return IMPLEMENTATION_SLOT; } - /** - * @notice Returns the version of the contract. - * @return Version string. - */ + /// @dev Returns the version of the implementation. + /// @return Version string. function version() external view virtual returns (string memory); // ************************************* // diff --git a/contracts/src/proxy/UUPSProxy.sol b/contracts/src/proxy/UUPSProxy.sol index f04f814e9..6193431b5 100644 --- a/contracts/src/proxy/UUPSProxy.sol +++ b/contracts/src/proxy/UUPSProxy.sol @@ -1,41 +1,27 @@ //SPDX-License-Identifier: MIT -// Adapted from -/** - * @authors: [@malatrax] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ pragma solidity 0.8.24; -/** - * @title UUPS Proxy - * @author Simon Malatrait - * @dev This contract implements a UUPS Proxy compliant with ERC-1967 & ERC-1822. - * @dev This contract delegates all calls to another contract (UUPS Proxiable) through a fallback function and the use of the `delegatecall` EVM instruction. - * @dev We refer to the Proxiable contract (as per ERC-1822) with `implementation`. - */ +/// @title UUPS Proxy +/// @author Simon Malatrait +/// @dev This contract implements a UUPS Proxy compliant with ERC-1967 & ERC-1822. +/// @dev This contract delegates all calls to another contract (UUPS Proxiable) through a fallback function and the use of the `delegatecall` EVM instruction. +/// @dev We refer to the Proxiable contract (as per ERC-1822) with `implementation`. +/// @dev Adapted from contract UUPSProxy { - /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is - * validated in the constructor. - * NOTE: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) - */ + /// @dev Storage slot with the address of the current implementation. + /// This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + /// validated in the constructor. + /// NOTE: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // ************************************* // // * Constructor * // // ************************************* // - /** - * @dev Initializes the upgradeable proxy with an initial implementation specified by `_implementation`. - * - * If `_data` is nonempty, it's used as data in a delegate call to `_implementation`. This will typically be an encoded - * function call, and allows initializing the storage of the proxy like a Solidity constructor. - */ + /// @dev Initializes the upgradeable proxy with an initial implementation specified by `_implementation`. + /// If `_data` is nonempty, it's used as data in a delegate call to `_implementation`. This will typically be an encoded + /// function call, and allows initializing the storage of the proxy like a Solidity constructor. constructor(address _implementation, bytes memory _data) { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) @@ -51,11 +37,8 @@ contract UUPSProxy { // * State Modifiers * // // ************************************* // - /** - * @dev Delegates the current call to `implementation`. - * - * NOTE: This function does not return to its internal call site, it will return directly to the external caller. - */ + /// @dev Delegates the current call to `implementation`. + /// NOTE: This function does not return to its internal call site, it will return directly to the external caller. function _delegate(address implementation) internal { assembly { // Copy msg.data. We take full control of memory in this inline assembly @@ -95,10 +78,8 @@ contract UUPSProxy { // * Fallback * // // ************************************* // - /** - * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other - * function in the contract matches the call data. - */ + /// @dev Fallback function that delegates calls to the address returned by `_implementation()`. + /// @dev Will run if no other function in the contract matches the call data. fallback() external payable { _delegate(_getImplementation()); } diff --git a/yarn.lock b/yarn.lock index de4ae4dd2..684a568a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5410,7 +5410,8 @@ __metadata: "@nomicfoundation/hardhat-chai-matchers": "npm:^2.0.8" "@nomicfoundation/hardhat-ethers": "npm:^3.0.8" "@nomiclabs/hardhat-solhint": "npm:^4.0.1" - "@openzeppelin/contracts": "npm:^5.1.0" + "@openzeppelin/contracts": "npm:^5.2.0" + "@openzeppelin/upgrades-core": "npm:^1.41.0" "@typechain/ethers-v6": "npm:^0.5.1" "@typechain/hardhat": "npm:^9.1.0" "@types/chai": "npm:^4.3.20" @@ -5422,23 +5423,23 @@ __metadata: dotenv: "npm:^16.4.5" eslint: "npm:^9.15.0" ethereumjs-util: "npm:^7.1.5" - ethers: "npm:^6.13.4" + ethers: "npm:^6.13.5" graphql: "npm:^16.9.0" graphql-request: "npm:^7.1.2" - hardhat: "npm:2.22.16" + hardhat: "npm:2.22.18" hardhat-contract-sizer: "npm:^2.10.0" hardhat-deploy: "npm:^0.14.0" hardhat-deploy-ethers: "npm:^0.4.2" - hardhat-deploy-tenderly: "npm:^0.2.0" + hardhat-deploy-tenderly: "npm:^0.2.1" hardhat-docgen: "npm:^1.3.0" - hardhat-gas-reporter: "npm:^2.2.1" + hardhat-gas-reporter: "npm:^2.2.2" hardhat-tracer: "npm:^3.1.0" hardhat-watcher: "npm:^2.5.0" node-fetch: "npm:^3.3.2" pino: "npm:^8.21.0" pino-pretty: "npm:^10.3.1" prettier: "npm:^3.3.3" - prettier-plugin-solidity: "npm:^1.4.1" + prettier-plugin-solidity: "npm:^1.4.2" shelljs: "npm:^0.8.5" solhint-plugin-prettier: "npm:^0.1.0" solidity-coverage: "npm:^0.8.13" @@ -6959,67 +6960,67 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/edr-darwin-arm64@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.6.4" - checksum: 10/9dd7ed96986f12973c715ba12031a5d91dff131baf1fffe58d7e2d1a990ea356b6192e4b74b46105f4831035262fb86f214a59b776aaa961c3a90a72d476794f +"@nomicfoundation/edr-darwin-arm64@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.7.0" + checksum: 10/be9ff1c9ece6875486c3eabd9cdddd90bbdbed9cecc376efc9aec0c7ce54fcb83b33adf4bab26fa134867a538cc26137c027c2aa2b7adf242ef0ef07fe240c56 languageName: node linkType: hard -"@nomicfoundation/edr-darwin-x64@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-darwin-x64@npm:0.6.4" - checksum: 10/6b283ea5382f7e0e78a89f6f4935f3eeb68744f7453ba5faddbb9e15d687d8cc096fe78b4ba0386671311200680e9ae7745436259d52a2bddb274394b9c30ca5 +"@nomicfoundation/edr-darwin-x64@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.7.0" + checksum: 10/5e89e2c51f470e0a88f089098c8771b55466c082b7d84148b114541f81ff182bab3383623170bf329a78ea6274571993fea20ebfe080f898e775f3457eda358f languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-gnu@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.6.4" - checksum: 10/8393b71350366f8029f19e61ebd7ec0f7588af50defab03ccb48d4f4c9b1a59d12aa2b73766cfbef9ae2bd5122798824a33b6cbbcf5fb34126e8532d9db1461b +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.7.0" + checksum: 10/7d77d116bc1b668ec83437795ae17150e70edfad700bd7335f7e7d072731649024c28cc7aca5799480cfef42b7ae52b3e1522051a04ce4f8924c716714176277 languageName: node linkType: hard -"@nomicfoundation/edr-linux-arm64-musl@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.6.4" - checksum: 10/ee9d14a0a395499e90019fdd162a8455c03035789ec7bcb3fe39d8967d3ec7edee84d5b4f30aa9d395fa4fc4faac39e8a00159ca8f8d73da794b17edec451134 +"@nomicfoundation/edr-linux-arm64-musl@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.7.0" + checksum: 10/de5c8a2a713eb9a6792a79c8b8ebb6f8de38018ab5bfc6bb35cd435a89c62e77bab550e334eadda9493b4134481d39f11208e3b480b86a0b4b703c0b3d05561a languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-gnu@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.6.4" - checksum: 10/161678a3dc357059886e3302b5db661d85f448e5a426217b9a99179e69a8a3a5a081819344e6854ab9b82799102247331e959540f19d9cf7872155070f21e82c +"@nomicfoundation/edr-linux-x64-gnu@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.7.0" + checksum: 10/33077f290bbb1e8ce178d50289bb6591c72a18b35b5f31e3e4051a9af6ec10312b21d47ed2d4938a6f64ee5b3e2413c3390fa0f4f5da5fb73dda7eb1c86bc742 languageName: node linkType: hard -"@nomicfoundation/edr-linux-x64-musl@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.6.4" - checksum: 10/22811238e96028a85af1e6e67cf242b9b24c9b71ee8883d87cf1624a936c5e812b7859e7ccfd6aa4b3f0df7d55e13f215774a2d913c3f546618a0d6871640e5b +"@nomicfoundation/edr-linux-x64-musl@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.7.0" + checksum: 10/48784d44e3dd8a7a0d52a0f3f7511f02e5d1d94678a1baa29bf5a1c0973a707c96e09835622b5483ed3698622abd34bf4338ed9f688f01cb8ce55edddf78cdb4 languageName: node linkType: hard -"@nomicfoundation/edr-win32-x64-msvc@npm:0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.6.4" - checksum: 10/509a8dd7c82a401f8c3e63a8621d602fd4398f3f0701109335756fb81795a575df7d6386126dcffee6659f50b0afc68b36702ab0fa7febb83bded82260629bd0 +"@nomicfoundation/edr-win32-x64-msvc@npm:0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.7.0" + checksum: 10/517897959478810d168f95274762f1565185026a8a908d289120f97344be33865104c2a07eb7277d5ea992f5db55790f63efe460fea61d1c2ed7879567828f15 languageName: node linkType: hard -"@nomicfoundation/edr@npm:^0.6.4": - version: 0.6.4 - resolution: "@nomicfoundation/edr@npm:0.6.4" +"@nomicfoundation/edr@npm:^0.7.0": + version: 0.7.0 + resolution: "@nomicfoundation/edr@npm:0.7.0" dependencies: - "@nomicfoundation/edr-darwin-arm64": "npm:0.6.4" - "@nomicfoundation/edr-darwin-x64": "npm:0.6.4" - "@nomicfoundation/edr-linux-arm64-gnu": "npm:0.6.4" - "@nomicfoundation/edr-linux-arm64-musl": "npm:0.6.4" - "@nomicfoundation/edr-linux-x64-gnu": "npm:0.6.4" - "@nomicfoundation/edr-linux-x64-musl": "npm:0.6.4" - "@nomicfoundation/edr-win32-x64-msvc": "npm:0.6.4" - checksum: 10/9bdcd316bcc8c5f0e137b4234e66455f427bc3046dc8f34b76aeed1e96eeedbe6a2c548970077ec4bd182d86ba630dfdcac6bce4b8d9a465b8f303c6a3aa6971 + "@nomicfoundation/edr-darwin-arm64": "npm:0.7.0" + "@nomicfoundation/edr-darwin-x64": "npm:0.7.0" + "@nomicfoundation/edr-linux-arm64-gnu": "npm:0.7.0" + "@nomicfoundation/edr-linux-arm64-musl": "npm:0.7.0" + "@nomicfoundation/edr-linux-x64-gnu": "npm:0.7.0" + "@nomicfoundation/edr-linux-x64-musl": "npm:0.7.0" + "@nomicfoundation/edr-win32-x64-msvc": "npm:0.7.0" + checksum: 10/b5c9546152574642b6d401b8da7f2a01cb98edd0da25aa2e7c16434e44b4134c699b50e8415449543701e3b722f5d6c8d8e4d8f699df3f7ebdb4acb907f0a794 languageName: node linkType: hard @@ -7485,14 +7486,14 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:^5.1.0": - version: 5.1.0 - resolution: "@openzeppelin/contracts@npm:5.1.0" - checksum: 10/c3954d09b206af23a98d352177b49e4b2796a818ee01f082b223d54e6d394b3741a4ea6012570359d2a705d5e932a7b906ee38b64e3987ababfa73bcaa5a4805 +"@openzeppelin/contracts@npm:^5.2.0": + version: 5.2.0 + resolution: "@openzeppelin/contracts@npm:5.2.0" + checksum: 10/d77e1bdfca6fa1c40b5a32bb92220ec633ef86cc0bf43a011ad218c26c9e763c70ace3fc1e148a0462394a3aed8c5d7445935ebfeb146ba1454abec66625e420 languageName: node linkType: hard -"@openzeppelin/upgrades-core@npm:^1.24.1": +"@openzeppelin/upgrades-core@npm:^1.24.1, @openzeppelin/upgrades-core@npm:^1.41.0": version: 1.41.0 resolution: "@openzeppelin/upgrades-core@npm:1.41.0" dependencies: @@ -8802,6 +8803,13 @@ __metadata: languageName: node linkType: hard +"@solidity-parser/parser@npm:^0.19.0": + version: 0.19.0 + resolution: "@solidity-parser/parser@npm:0.19.0" + checksum: 10/2136708ecc988b534efcf836e95f4f02a1452ab0c026438014c35ce31b26dc011cc8c512d502fc7bcb968f850ab7e524838292bc36cad6a144fedb4c29685587 + languageName: node + linkType: hard + "@sphinxxxx/color-conversion@npm:^2.2.2": version: 2.2.2 resolution: "@sphinxxxx/color-conversion@npm:2.2.2" @@ -18390,7 +18398,22 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^6.13.4, ethers@npm:^6.9.0": +"ethers@npm:^6.13.5": + version: 6.13.5 + resolution: "ethers@npm:6.13.5" + dependencies: + "@adraffy/ens-normalize": "npm:1.10.1" + "@noble/curves": "npm:1.2.0" + "@noble/hashes": "npm:1.3.2" + "@types/node": "npm:22.7.5" + aes-js: "npm:4.0.0-beta.5" + tslib: "npm:2.7.0" + ws: "npm:8.17.1" + checksum: 10/ccba21a83679fb6a7c3eb9d187593501565d140064f2db28057a64d6707678bacf2adf4555882c4814688246da73173560df81fd3361fd2f227b5d3c75cb8685 + languageName: node + linkType: hard + +"ethers@npm:^6.9.0": version: 6.13.4 resolution: "ethers@npm:6.13.4" dependencies: @@ -20303,16 +20326,16 @@ __metadata: languageName: node linkType: hard -"hardhat-deploy-tenderly@npm:^0.2.0": - version: 0.2.0 - resolution: "hardhat-deploy-tenderly@npm:0.2.0" +"hardhat-deploy-tenderly@npm:^0.2.1": + version: 0.2.1 + resolution: "hardhat-deploy-tenderly@npm:0.2.1" dependencies: axios: "npm:^0.24.0" js-yaml: "npm:^4.1.0" peerDependencies: hardhat: ^2.0.0 - hardhat-deploy: ^0.11.10 - checksum: 10/3d7d49f3787bcc793a36c4514acdd0d19d8e1383e59f8bebc835bb1a555e8c5cbdb83aec3287312ddd9b796bfb0e1b96b41245722a4de38d85060c78bc9055d3 + hardhat-deploy: 0.x + checksum: 10/0c62f8b84e65541e98446b4d4098d1cfb0aa05dbf2eda3fb0825cc86be04939ab9c63aef3a62ed63b7d26f301f65a1bfa0b127e4a2a00a6136d87d1a3672d73f languageName: node linkType: hard @@ -20365,14 +20388,14 @@ __metadata: languageName: node linkType: hard -"hardhat-gas-reporter@npm:^2.2.1": - version: 2.2.1 - resolution: "hardhat-gas-reporter@npm:2.2.1" +"hardhat-gas-reporter@npm:^2.2.2": + version: 2.2.2 + resolution: "hardhat-gas-reporter@npm:2.2.2" dependencies: "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/bytes": "npm:^5.7.0" "@ethersproject/units": "npm:^5.7.0" - "@solidity-parser/parser": "npm:^0.18.0" + "@solidity-parser/parser": "npm:^0.19.0" axios: "npm:^1.6.7" brotli-wasm: "npm:^2.0.1" chalk: "npm:4.1.2" @@ -20386,7 +20409,7 @@ __metadata: viem: "npm:2.7.14" peerDependencies: hardhat: ^2.16.0 - checksum: 10/af8bd86400035ab1ed574c5f782e7ee408fff75ea6cfcd56ab87bf80c2e911e9fbe943b528ad17844abad8ff8f63daa24aae391ccbf3e07a9970be66f949bd3d + checksum: 10/32a13011b414bae887741907bb2ea23cda0e645fb4fdfce87bed5a37be95f26a723ccc4140b413ec215832d6cc0fa5ff517f6d47af1bbedee5508bb5432269d7 languageName: node linkType: hard @@ -20416,13 +20439,13 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:2.22.16": - version: 2.22.16 - resolution: "hardhat@npm:2.22.16" +"hardhat@npm:2.22.18": + version: 2.22.18 + resolution: "hardhat@npm:2.22.18" dependencies: "@ethersproject/abi": "npm:^5.1.2" "@metamask/eth-sig-util": "npm:^4.0.0" - "@nomicfoundation/edr": "npm:^0.6.4" + "@nomicfoundation/edr": "npm:^0.7.0" "@nomicfoundation/ethereumjs-common": "npm:4.0.4" "@nomicfoundation/ethereumjs-tx": "npm:5.0.4" "@nomicfoundation/ethereumjs-util": "npm:9.0.4" @@ -20474,7 +20497,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 10/77e3f0447e42265107242827f8efd8346224c79001a0eea2d0d71bcaeee1ab9ed1e1fff47c28e36d19e1b3c386707dc60dd3d5e22b09da15f8b2ea5feb5ff943 + checksum: 10/521d46b31d15cda4c38b4e1995ba47bd34386bd10aa0554be4e2e7c9c889d25ce57c4018c76eda94168da5bab697ab5ad98b2715a1033247a0414a8853787bdd languageName: node linkType: hard @@ -29035,15 +29058,15 @@ __metadata: languageName: node linkType: hard -"prettier-plugin-solidity@npm:^1.4.1": - version: 1.4.1 - resolution: "prettier-plugin-solidity@npm:1.4.1" +"prettier-plugin-solidity@npm:^1.4.1, prettier-plugin-solidity@npm:^1.4.2": + version: 1.4.2 + resolution: "prettier-plugin-solidity@npm:1.4.2" dependencies: - "@solidity-parser/parser": "npm:^0.18.0" - semver: "npm:^7.5.4" + "@solidity-parser/parser": "npm:^0.19.0" + semver: "npm:^7.6.3" peerDependencies: prettier: ">=2.3.0" - checksum: 10/8bd0b20cd1a0973bfdbb4e035d5dae9ff229a7f6475c4e31e99b85d74f77625c0a874835df23e51d36750139aa2e5c5d900a366d1cdff16edfe52b3550b7e2da + checksum: 10/5f4ca400275c860bbca7ba3ee316682bec04a760a816e903f6a528acc61ef3d2eda9a81edb7e38449b79196d2946db44424fbab78944d2e8bb32f8fed31363bc languageName: node linkType: hard From c0f2f89757f685d7fa2a36815a0f536a170ca893 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 22 Jan 2025 00:35:10 +0000 Subject: [PATCH 20/42] feat: added support for ERC1155 --- .../dispute-kits/DisputeKitGated.sol | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index c33bd0f13..ffd988298 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -8,23 +8,30 @@ pragma solidity 0.8.24; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - import "../KlerosCore.sol"; import "../interfaces/IDisputeKit.sol"; import "../../proxy/UUPSProxiable.sol"; import "../../proxy/Initializable.sol"; -interface IToken { +interface IBalanceHolder { /// @dev Returns the number of tokens in `owner` account. + /// @dev Compatible with ERC-20 and ERC-721. /// @param owner The address of the owner. /// @return balance The number of tokens in `owner` account. function balanceOf(address owner) external view returns (uint256 balance); } +interface IBalanceHolderERC1155 { + /// @dev Returns the balance of an ERC-1155 token. + /// @param account The address of the token holder + /// @param id ID of the token + /// @return The token balance + function balanceOf(address account, uint256 id) external view returns (uint256); +} + /// @title DisputeKitGated /// Dispute kit implementation adapted from DisputeKitClassic -/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate`, +/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate` where `tokenGate` is an ERC20, ERC721 or ERC1155 /// - a vote aggregation system: plurality, /// - an incentive system: equal split between coherent votes, /// - an appeal system: fund 2 choices only, vote on any choice. @@ -76,7 +83,9 @@ contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { KlerosCore public core; // The Kleros Core arbitrator Dispute[] public disputes; // Array of the locally created disputes. mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID. - IToken public tokenGate; // The token used for gating access. + address public tokenGate; // The token used for gating access. + uint256 public tokenId; // Only used for ERC-1155 + bool public isERC1155; // True if the tokenGate is an ERC-1155, false otherwise. // ************************************* // // * Events * // @@ -161,10 +170,20 @@ contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { /// @param _governor The governor's address. /// @param _core The KlerosCore arbitrator. /// @param _tokenGate The token used for gating access. - function initialize(address _governor, KlerosCore _core, IToken _tokenGate) external reinitializer(1) { + /// @param _tokenId The token ID for ERC-1155 (ignored for other token types) + /// @param _isERC1155 Whether the token is ERC-1155 + function initialize( + address _governor, + KlerosCore _core, + address _tokenGate, + uint256 _tokenId, + bool _isERC1155 + ) external reinitializer(1) { governor = _governor; core = _core; tokenGate = _tokenGate; + tokenId = _tokenId; + isERC1155 = _isERC1155; } // ************************ // @@ -202,10 +221,20 @@ contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { core = KlerosCore(_core); } - /// @dev Changes the `tokenGate` storage variable. + /// @dev Changes the `tokenGate` to an ERC-20 or ERC-721 token. /// @param _tokenGate The new value for the `tokenGate` storage variable. - function changeTokenGate(address _tokenGate) external onlyByGovernor { - tokenGate = IToken(_tokenGate); + function changeTokenGateERC20OrERC721(address _tokenGate) external onlyByGovernor { + tokenGate = _tokenGate; + isERC1155 = false; + } + + /// @dev Changes the `tokenGate` to an ERC-1155 token. + /// @param _tokenGate The new value for the `tokenGate` storage variable. + /// @param _tokenId The new value for the `tokenId` storage variable. + function changeTokenGateERC1155(address _tokenGate, uint256 _tokenId) external onlyByGovernor { + tokenGate = _tokenGate; + tokenId = _tokenId; + isERC1155 = true; } // ************************************* // @@ -630,10 +659,12 @@ contract DisputeKitGated is IDisputeKit, Initializable, UUPSProxiable { .getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1) .pnkAtStakePerJuror; (uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID); - if (totalStaked < totalLocked + lockedAmountPerJuror) { - return false; + if (totalStaked < totalLocked + lockedAmountPerJuror) return false; + + if (isERC1155) { + return IBalanceHolderERC1155(tokenGate).balanceOf(_juror, tokenId) > 0; } else { - return tokenGate.balanceOf(_juror) > 0; + return IBalanceHolder(tokenGate).balanceOf(_juror) > 0; } } } From 9f953c21904af2c81e5a3c88de0468d1126db61f Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 22 Jan 2025 00:38:09 +0000 Subject: [PATCH 21/42] refactor: minor rename of DisputeKitGated interfaces --- .../src/arbitration/dispute-kits/DisputeKitGated.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index f4dffc787..38aaa2a36 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -10,14 +10,15 @@ pragma solidity 0.8.24; import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; -interface IERC20OrERC721 { +interface IBalanceHolder { /// @dev Returns the number of tokens in `owner` account. + /// @dev Compatible with ERC-20 and ERC-721. /// @param owner The address of the owner. /// @return balance The number of tokens in `owner` account. function balanceOf(address owner) external view returns (uint256 balance); } -interface IERC1155 { +interface IBalanceHolderERC1155 { /// @dev Returns the balance of an ERC-1155 token. /// @param account The address of the token holder /// @param id ID of the token @@ -109,9 +110,9 @@ contract DisputeKitGated is DisputeKitClassicBase { if (!super._postDrawCheck(_round, _coreDisputeID, _juror)) return false; if (isERC1155) { - return IERC1155(tokenGate).balanceOf(_juror, tokenId) > 0; + return IBalanceHolderERC1155(tokenGate).balanceOf(_juror, tokenId) > 0; } else { - return IERC20OrERC721(tokenGate).balanceOf(_juror) > 0; + return IBalanceHolder(tokenGate).balanceOf(_juror) > 0; } } } From f3f05ab18aa583bd694b5d3dd273fe1fdb3b0534 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 22 Jan 2025 20:48:35 +0000 Subject: [PATCH 22/42] fix: missing web-devtools dependency --- web-devtools/package.json | 3 +++ yarn.lock | 3 +++ 2 files changed, 6 insertions(+) diff --git a/web-devtools/package.json b/web-devtools/package.json index 3d337091d..21cf78af9 100644 --- a/web-devtools/package.json +++ b/web-devtools/package.json @@ -54,14 +54,17 @@ "@wagmi/connectors": "^5.5.0", "@wagmi/core": "^2.15.0", "@web3modal/wagmi": "^4.2.3", + "@yornaath/batshit": "^0.9.0", "graphql": "^16.9.0", "graphql-request": "^7.1.2", "next": "14.2.18", "react": "^18.3.1", "react-dom": "^18.3.1", "react-is": "^18.3.1", + "react-loading-skeleton": "^3.5.0", "react-markdown": "^9.0.1", "react-toastify": "^10.0.6", + "react-use": "^17.5.1", "styled-components": "^5.3.3", "typewriter-effect": "^2.21.0", "vanilla-jsoneditor": "^0.21.6", diff --git a/yarn.lock b/yarn.lock index 0c89f0d06..363126cb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5571,6 +5571,7 @@ __metadata: "@wagmi/connectors": "npm:^5.5.0" "@wagmi/core": "npm:^2.15.0" "@web3modal/wagmi": "npm:^4.2.3" + "@yornaath/batshit": "npm:^0.9.0" eslint: "npm:^9.15.0" eslint-config-next: "npm:^15.0.3" eslint-config-prettier: "npm:^9.1.0" @@ -5583,8 +5584,10 @@ __metadata: react: "npm:^18.3.1" react-dom: "npm:^18.3.1" react-is: "npm:^18.3.1" + react-loading-skeleton: "npm:^3.5.0" react-markdown: "npm:^9.0.1" react-toastify: "npm:^10.0.6" + react-use: "npm:^17.5.1" rimraf: "npm:^6.0.1" styled-components: "npm:^5.3.3" ts-node: "npm:^10.9.2" From 45daa0e5eb125316e7e0a95181d98d265eb4c389 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 23 Jan 2025 13:38:59 +0530 Subject: [PATCH 23/42] fix: prettier-config --- prettier-config/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prettier-config/index.js b/prettier-config/index.js index 9f4d7bbcb..e99074590 100644 --- a/prettier-config/index.js +++ b/prettier-config/index.js @@ -13,6 +13,7 @@ module.exports = { { files: ["*.sol"], options: { + parser: "solidity-parse", printWidth: 120, tabWidth: 4, useTabs: false, @@ -22,5 +23,5 @@ module.exports = { }, }, ], - plugins: [require("prettier-plugin-solidity")], + plugins: ["prettier-plugin-solidity"], }; From de7fdd2a6bb52ae91e403249ddc1d6cc5569d2e4 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 23 Jan 2025 16:26:54 +0530 Subject: [PATCH 24/42] refactor(web): update-spam-api-call --- web/src/hooks/useSpamEvidence.ts | 22 +++-- .../Cases/CaseDetails/Evidence/index.tsx | 80 +++++++++---------- 2 files changed, 47 insertions(+), 55 deletions(-) diff --git a/web/src/hooks/useSpamEvidence.ts b/web/src/hooks/useSpamEvidence.ts index 93f80d6b3..8b420001c 100644 --- a/web/src/hooks/useSpamEvidence.ts +++ b/web/src/hooks/useSpamEvidence.ts @@ -1,21 +1,19 @@ import { useQuery } from "@tanstack/react-query"; -import { request } from "graphql-request"; +import { gql, request } from "graphql-request"; import { isKlerosNeo, isKlerosUniversity, isTestnetDeployment } from "src/consts"; -import { graphql } from "src/graphql"; import { isUndefined } from "src/utils"; -const spamEvidenceQuery = graphql(` - query SpamEvidences($deployment: CourtV2Deployment!) { - courtv2EvidenceSpamsByDeployment(deployment: $deployment) { - disputeEvidenceIndex - dispute +const spamEvidenceQuery = gql` + query SpamEvidences($deployment: CourtV2Deployment!, $evidenceGroupId: String!) { + courtv2EvidenceSpamsByGroupId(deployment: $deployment, evidenceGroupId: $evidenceGroupId) { + evidenceIds } } -`); +`; type SpamEvidences = { - courtv2EvidenceSpamsByDeployment: { disputeEvidenceIndex: string; dispute: string }[]; + courtv2EvidenceSpamsByGroupId: { evidenceIds: string[] }; }; const getAtlasDeployment = () => { @@ -31,10 +29,10 @@ const getAtlasDeployment = () => { }; const atlasUri = import.meta.env.REACT_APP_ATLAS_URI; -export const useSpamEvidence = () => { - const isEnabled = !isUndefined(atlasUri); +export const useSpamEvidence = (evidenceGroupId: string) => { + const isEnabled = !isUndefined(atlasUri) && !isUndefined(evidenceGroupId); - const variables = { deployment: getAtlasDeployment() }; + const variables = { deployment: getAtlasDeployment(), evidenceGroupId }; return useQuery({ queryKey: [`evidenceSpamQuery`], enabled: isEnabled, diff --git a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx index 76bbce32e..0a8adde07 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx @@ -85,7 +85,7 @@ const Evidence: React.FC = () => { const [search, setSearch] = useState(); const [debouncedSearch, setDebouncedSearch] = useState(); const [showSpam, setShowSpam] = useState(false); - const { data: spamEvidences } = useSpamEvidence(); + const { data: spamEvidences } = useSpamEvidence(disputeData?.dispute?.externalDisputeId?.toString()); const { data } = useEvidences(disputeData?.dispute?.externalDisputeId?.toString(), debouncedSearch); @@ -100,23 +100,11 @@ const Evidence: React.FC = () => { latestEvidence.scrollIntoView({ behavior: "smooth" }); }, [ref]); - const flattenedSpamEvidences = useMemo( - () => - spamEvidences?.courtv2EvidenceSpamsByDeployment.reduce((acc, current) => { - if (current.dispute === id) { - acc.push(current.disputeEvidenceIndex); - return acc; - } - return acc; - }, []), - [id, spamEvidences] - ); - const isSpam = useCallback( (evidenceId: string) => { - return Boolean(flattenedSpamEvidences?.includes(evidenceId)); + return Boolean(spamEvidences?.courtv2EvidenceSpamsByGroupId.evidenceIds?.includes(evidenceId)); }, - [flattenedSpamEvidences] + [spamEvidences] ); const evidences = useMemo(() => { @@ -131,38 +119,44 @@ const Evidence: React.FC = () => { {evidences?.realEvidences ? ( - evidences?.realEvidences.map( - ({ evidence, sender, timestamp, transactionHash, name, description, fileURI, evidenceIndex }) => ( - - ) - ) - ) : ( - - )} - {evidences?.spamEvidences.length !== 0 ? ( <> - - {showSpam ? ( - evidences?.spamEvidences.map( - ({ evidence, sender, timestamp, transactionHash, name, description, fileURI, evidenceIndex }) => ( - - ) + {evidences?.realEvidences.map( + ({ evidence, sender, timestamp, transactionHash, name, description, fileURI, evidenceIndex }) => ( + ) - ) : ( - setShowSpam(true)}>Show likely spam )} + {spamEvidences && evidences?.spamEvidences.length !== 0 ? ( + <> + + {showSpam ? ( + <> + setShowSpam(false)}>Hide spam + {evidences?.spamEvidences.map( + ({ evidence, sender, timestamp, transactionHash, name, description, fileURI, evidenceIndex }) => ( + + ) + )} + + ) : ( + setShowSpam(true)}>Show likely spam + )} + + ) : null} - ) : null} + ) : ( + + )} + {data && data.evidences.length === 0 ? There is no evidence submitted yet : null} ); From 19908c6b11f00713e3c47681e47a729b62fa3e05 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 23 Jan 2025 17:30:43 +0530 Subject: [PATCH 25/42] feat(web): file-restrictions-message --- .../Evidence/SubmitEvidenceModal.tsx | 14 +++++++++---- web/src/pages/Resolver/Policy/index.tsx | 21 ++++++++++++------- web/src/utils/index.ts | 19 +++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx b/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx index d8056574a..12916f302 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx @@ -4,13 +4,13 @@ import styled from "styled-components"; import Modal from "react-modal"; import { useWalletClient, usePublicClient, useConfig } from "wagmi"; -import { useAtlasProvider, Roles } from "@kleros/kleros-app"; +import { Roles, useAtlasProvider } from "@kleros/kleros-app"; import { Textarea, Button, FileUploader } from "@kleros/ui-components-library"; import { simulateEvidenceModuleSubmitEvidence } from "hooks/contracts/generated"; import { wrapWithToast, errorToast, infoToast, successToast } from "utils/wrapWithToast"; -import { isEmpty } from "src/utils"; +import { getFileUploaderMsg, isEmpty } from "src/utils"; import EnsureAuth from "components/EnsureAuth"; import { EnsureChain } from "components/EnsureChain"; @@ -43,6 +43,7 @@ const StyledTextArea = styled(Textarea)` const StyledFileUploader = styled(FileUploader)` width: 100%; + margin-bottom: 50px; `; const ButtonArea = styled.div` @@ -62,7 +63,7 @@ const SubmitEvidenceModal: React.FC<{ const [isSending, setIsSending] = useState(false); const [message, setMessage] = useState(""); const [file, setFile] = useState(); - const { uploadFile } = useAtlasProvider(); + const { uploadFile, roleRestrictions } = useAtlasProvider(); const isDisabled = useMemo(() => isSending || isEmpty(message), [isSending, message]); @@ -98,7 +99,11 @@ const SubmitEvidenceModal: React.FC<{ onChange={(e) => setMessage(e.target.value)} placeholder="Your Arguments" /> - setFile(file)} /> + setFile(file)} + msg={getFileUploaderMsg(Roles.Evidence, roleRestrictions)} + variant="info" + /> @@ -120,6 +125,7 @@ const constructEvidence = async ( if (file) { infoToast("Uploading to IPFS"); fileURI = await uploadFile(file, Roles.Evidence).catch((err) => { + // eslint-disable-next-line no-console console.log(err); errorToast(`Upload failed: ${err?.message}`); return null; diff --git a/web/src/pages/Resolver/Policy/index.tsx b/web/src/pages/Resolver/Policy/index.tsx index cee5bd0b8..584a94279 100644 --- a/web/src/pages/Resolver/Policy/index.tsx +++ b/web/src/pages/Resolver/Policy/index.tsx @@ -1,10 +1,14 @@ import React from "react"; import styled, { css } from "styled-components"; +import { useAtlasProvider, Roles } from "@kleros/kleros-app"; import { FileUploader } from "@kleros/ui-components-library"; -import { useAtlasProvider, Roles } from "@kleros/kleros-app"; import { useNewDisputeContext } from "context/NewDisputeContext"; +import useIsDesktop from "hooks/useIsDesktop"; +import { errorToast, infoToast, successToast } from "utils/wrapWithToast"; + +import { getFileUploaderMsg } from "src/utils"; import { landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; @@ -12,7 +16,6 @@ import { responsiveSize } from "styles/responsiveSize"; import Header from "pages/Resolver/Header"; import NavigationButtons from "../NavigationButtons"; -import { errorToast, infoToast, successToast } from "utils/wrapWithToast"; const Container = styled.div` display: flex; @@ -38,19 +41,23 @@ const StyledLabel = styled.label` const StyledFileUploader = styled(FileUploader)` width: 84vw; - margin-bottom: ${responsiveSize(52, 32)}; + margin-bottom: ${responsiveSize(150, 72)}; ${landscapeStyle( () => css` width: ${responsiveSize(442, 700, 900)}; ` )} + small { + white-space: pre-line; + text-align: start; + } `; const Policy: React.FC = () => { const { disputeData, setDisputeData, setIsPolicyUploading } = useNewDisputeContext(); - const { uploadFile } = useAtlasProvider(); - + const { uploadFile, roleRestrictions } = useAtlasProvider(); + const isDesktop = useIsDesktop(); const handleFileUpload = (file: File) => { setIsPolicyUploading(true); infoToast("Uploading Policy to IPFS"); @@ -78,8 +85,8 @@ const Policy: React.FC = () => { diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts index 0bd973663..9cc7a9bcc 100644 --- a/web/src/utils/index.ts +++ b/web/src/utils/index.ts @@ -1,3 +1,5 @@ +import { Roles } from "@kleros/kleros-app"; + import { DEFAULT_CHAIN, getChain } from "consts/chains"; export const isUndefined = (maybeObject: any): maybeObject is undefined | null => @@ -10,3 +12,20 @@ export const isEmpty = (str: string): boolean => str.trim() === ""; export const getTxnExplorerLink = (hash: string) => `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/tx/${hash}`; + +type Role = { + name: string; + restriction: { + maxSize: number; + allowedMimeTypes: string[]; + }; +}; + +export const getFileUploaderMsg = (role: Roles, roleRestrictions?: Role[]) => { + if (!roleRestrictions) return; + const restrictions = roleRestrictions.find((supportedRoles) => Roles[supportedRoles.name] === role); + + if (!restrictions) return; + + return `Allowed file types: [${restrictions.restriction.allowedMimeTypes.map((type) => type.split("/")?.[1] ?? null).join(", ")}], Max allowed size: ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} MB.`; +}; From c813e76fa4b6c47d281742ca9ac45c80e9163046 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 23 Jan 2025 19:15:05 +0530 Subject: [PATCH 26/42] refactor(web): update-message-creator-function --- web/src/utils/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts index 9cc7a9bcc..df47d9aac 100644 --- a/web/src/utils/index.ts +++ b/web/src/utils/index.ts @@ -27,5 +27,12 @@ export const getFileUploaderMsg = (role: Roles, roleRestrictions?: Role[]) => { if (!restrictions) return; - return `Allowed file types: [${restrictions.restriction.allowedMimeTypes.map((type) => type.split("/")?.[1] ?? null).join(", ")}], Max allowed size: ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} MB.`; + return `Allowed file types: [ ${restrictions.restriction.allowedMimeTypes + .map((type) => { + const [prefix, suffix] = type.split("/"); + if (!suffix) return prefix ?? null; + + return suffix === "*" ? prefix : suffix; + }) + .join(", ")} ], Max allowed size: ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} MB.`; }; From 73b1551eb9f01a71e52a6290c6ffa46b3a3e4cd0 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Fri, 24 Jan 2025 14:13:44 +0530 Subject: [PATCH 27/42] refactor(web): feedback-addressing --- web/src/pages/Resolver/Policy/index.tsx | 2 +- web/src/utils/index.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/pages/Resolver/Policy/index.tsx b/web/src/pages/Resolver/Policy/index.tsx index 584a94279..66c8b5888 100644 --- a/web/src/pages/Resolver/Policy/index.tsx +++ b/web/src/pages/Resolver/Policy/index.tsx @@ -86,7 +86,7 @@ const Policy: React.FC = () => { diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts index df47d9aac..dcb9872f4 100644 --- a/web/src/utils/index.ts +++ b/web/src/utils/index.ts @@ -26,13 +26,14 @@ export const getFileUploaderMsg = (role: Roles, roleRestrictions?: Role[]) => { const restrictions = roleRestrictions.find((supportedRoles) => Roles[supportedRoles.name] === role); if (!restrictions) return; - - return `Allowed file types: [ ${restrictions.restriction.allowedMimeTypes + const typesString = restrictions.restriction.allowedMimeTypes .map((type) => { const [prefix, suffix] = type.split("/"); if (!suffix) return prefix ?? null; return suffix === "*" ? prefix : suffix; }) - .join(", ")} ], Max allowed size: ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} MB.`; + .join(", "); + + return `Allowed file types: [${typesString}], Max allowed size: ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} MB.`; }; From af973c7a6e083b065129ec0b87841e178d6f7bc5 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 24 Jan 2025 15:53:13 +0000 Subject: [PATCH 28/42] feat: new oracle court, edited spanish non-technical court, scripts improvements --- contracts/config/courts.v2.devnet.json | 49 ++++++++++++++++--- contracts/config/courts.v2.mainnet-neo.json | 18 ++++++- contracts/config/courts.v2.testnet.json | 37 +++++++++++++- contracts/config/policies.v2.devnet.json | 20 +++++++- contracts/config/policies.v2.mainnet-neo.json | 16 ++++-- contracts/config/policies.v2.testnet.json | 20 +++++++- .../policies.v2/Consumo-y-Vecindad.json | 6 +++ .../policies.v2/Oracle-Court-Policy.json | 6 +++ contracts/package.json | 6 +++ contracts/scripts/populateCourts.ts | 17 +++++-- contracts/scripts/populatePolicyRegistry.ts | 9 ++-- 11 files changed, 180 insertions(+), 24 deletions(-) create mode 100644 contracts/config/policies.v2/Consumo-y-Vecindad.json create mode 100644 contracts/config/policies.v2/Oracle-Court-Policy.json diff --git a/contracts/config/courts.v2.devnet.json b/contracts/config/courts.v2.devnet.json index c1fdd831c..23e9011c9 100644 --- a/contracts/config/courts.v2.devnet.json +++ b/contracts/config/courts.v2.devnet.json @@ -1,11 +1,12 @@ [ { + "name": "General Court", "id": 1, "parent": 0, "hiddenVotes": true, - "minStake": "520000000000000000000", + "minStake": "1500000000000000000", "alpha": "5000", - "feeForJuror": "15000000000000000000", + "feeForJuror": "100000000000", "jurorsForCourtJump": "511", "timesPerPeriod": [ 280800, @@ -15,13 +16,14 @@ ] }, { + "name": "Curation", "id": 2, "parent": 1, "hiddenVotes": false, - "minStake": "520000000000000000000", + "minStake": "2000000000000000000", "alpha": "3100", - "feeForJuror": "6900000000000000000", - "jurorsForCourtJump": "30", + "feeForJuror": "100000000000", + "jurorsForCourtJump": "31", "timesPerPeriod": [ 140400, 291600, @@ -30,12 +32,13 @@ ] }, { + "name": "English Language", "id": 3, "parent": 1, "hiddenVotes": false, - "minStake": "1200000000000000000000", + "minStake": "2000000000000000000", "alpha": "5000", - "feeForJuror": "22000000000000000000", + "feeForJuror": "100000000000", "jurorsForCourtJump": "63", "timesPerPeriod": [ 280800, @@ -43,5 +46,37 @@ 437400, 291600 ] + }, + { + "name": "Corte de Disputas de Consumo y Vecindad", + "id": 4, + "parent": 1, + "hiddenVotes": false, + "minStake": "2000000000000000000", + "alpha": "5000", + "feeForJuror": "100000000000", + "jurorsForCourtJump": "63", + "timesPerPeriod": [ + 140400, + 291600, + 291600, + 194400 + ] + }, + { + "name": "Oracle Court", + "id": 5, + "parent": 1, + "hiddenVotes": false, + "minStake": "2000000000000000000", + "alpha": "5000", + "feeForJuror": "100000000000", + "jurorsForCourtJump": "31", + "timesPerPeriod": [ + 140400, + 291600, + 291600, + 194400 + ] } ] diff --git a/contracts/config/courts.v2.mainnet-neo.json b/contracts/config/courts.v2.mainnet-neo.json index cceded59d..16e657873 100644 --- a/contracts/config/courts.v2.mainnet-neo.json +++ b/contracts/config/courts.v2.mainnet-neo.json @@ -448,7 +448,7 @@ ] }, { - "name": "Blockchain No Técnica", + "name": "Corte de Disputas de Consumo y Vecindad", "id": 29, "parent": 23, "hiddenVotes": false, @@ -462,5 +462,21 @@ 216000, 216000 ] + }, + { + "name": "Oracle Court", + "id": 30, + "parent": 1, + "hiddenVotes": false, + "minStake": "5000000000000000000000", + "feeForJuror": "6900000000000000", + "alpha": "5000", + "jurorsForCourtJump": "31", + "timesPerPeriod": [ + 280800, + 583200, + 583200, + 388800 + ] } ] diff --git a/contracts/config/courts.v2.testnet.json b/contracts/config/courts.v2.testnet.json index a50c47f4e..1c15a017c 100644 --- a/contracts/config/courts.v2.testnet.json +++ b/contracts/config/courts.v2.testnet.json @@ -1,5 +1,6 @@ [ { + "name": "General Court", "id": 1, "parent": 0, "hiddenVotes": true, @@ -15,13 +16,14 @@ ] }, { + "name": "Curation", "id": 2, "parent": 1, "hiddenVotes": false, "minStake": "200000000000000000000", "alpha": "3100", "feeForJuror": "10000000000000", - "jurorsForCourtJump": "30", + "jurorsForCourtJump": "31", "timesPerPeriod": [ 43200, 43200, @@ -30,6 +32,7 @@ ] }, { + "name": "English Language", "id": 3, "parent": 1, "hiddenVotes": false, @@ -43,5 +46,37 @@ 43200, 43200 ] + }, + { + "name": "Corte de Disputas de Consumo y Vecindad", + "id": 4, + "parent": 1, + "hiddenVotes": false, + "minStake": "200000000000000000000", + "alpha": "5000", + "feeForJuror": "10000000000000", + "jurorsForCourtJump": "63", + "timesPerPeriod": [ + 43200, + 43200, + 43200, + 43200 + ] + }, + { + "name": "Oracle Court", + "id": 5, + "parent": 1, + "hiddenVotes": false, + "minStake": "200000000000000000000", + "alpha": "5000", + "feeForJuror": "10000000000000", + "jurorsForCourtJump": "31", + "timesPerPeriod": [ + 43200, + 43200, + 43200, + 43200 + ] } ] diff --git a/contracts/config/policies.v2.devnet.json b/contracts/config/policies.v2.devnet.json index db5a1939c..7d11ed580 100644 --- a/contracts/config/policies.v2.devnet.json +++ b/contracts/config/policies.v2.devnet.json @@ -8,7 +8,7 @@ }, { "name": "Curation", - "description": "**Court purpose:** \n\n In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", + "description": "**Court purpose:** \n\nIn this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", "summary": "", "requiredSkills": "No particular skills are required.", "court": 2, @@ -16,10 +16,26 @@ }, { "name": "English Language", - "description": "**Court purpose:** \n\n In this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "description": "**Court purpose:** \n\nIn this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", "requiredSkills": "This court requires an advanced level of English. Jurors who are not native English speakers are advised to stake into this court only if they have C1+ level of English.\n\nThe following tests evaluates a C1 level: Cambridge Advanced (CAE), BEC Higher, BULATS score 75+, CLB/CELPIP 8+, CAEL 70+, IELTS level 7, TOEFL 110+, TOEIC score 880+.", "court": 3, "uri": "/ipfs/QmPKVfEdGsERypkHBR4ZhGbkpqEUFSJcddB8xmGJjSqfFv/English-Language-Court-Policy.json" + }, + { + "name": "Corte de Disputas de Consumo y Vecindad", + "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", + "summary": "", + "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", + "court": 4, + "uri": "/ipfs/QmVnqEpQpAeYvdfuPjxnGEvZG7ui7kFz6hhaskpNRgpjk1" + }, + { + "name": "Oracle Court", + "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function.", + "court": 5, + "uri": "/ipfs/QmRNKyST212j2DLLkA7WDBDH65tcGAVxiTkJ54LsZkVat7" } ] diff --git a/contracts/config/policies.v2.mainnet-neo.json b/contracts/config/policies.v2.mainnet-neo.json index 6e12a02b7..ff7f04ff5 100644 --- a/contracts/config/policies.v2.mainnet-neo.json +++ b/contracts/config/policies.v2.mainnet-neo.json @@ -221,11 +221,19 @@ "uri": "/ipfs/QmQeHpuaL9RViwKnPNRMCAbPakdPSDefBmhPpMAi45vRLS/xDai-Spanish-Curation-Court-Policy.json" }, { - "name": "Blockchain No Técnica", - "description": "", + "name": "Corte de Disputas de Consumo y Vecindad", + "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", "summary": "", - "requiredSkills": "**Habilidades Requeridas:** \n\n Los jurados no necesitan un conocimiento profundo de blockchain ni habilidades de programación. Pero necesitan conocimiento para leer exploradores de blockchain, buscar criptoactivos listados en exchanges, entender cómo verificar una transacción y analizar datos on-chain.", + "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", "court": 29, - "uri": "/ipfs/QmVxh7KmFrimGz6WMvLMRXZhwQFDoj28KEYLTpYoUTGwAj/xDai-Blockchain-No-Tecnica-Policy.json" + "uri": "/ipfs/QmVnqEpQpAeYvdfuPjxnGEvZG7ui7kFz6hhaskpNRgpjk1" + }, + { + "name": "Oracle Court", + "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function.", + "court": 30, + "uri": "/ipfs/QmRNKyST212j2DLLkA7WDBDH65tcGAVxiTkJ54LsZkVat7" } ] diff --git a/contracts/config/policies.v2.testnet.json b/contracts/config/policies.v2.testnet.json index db5a1939c..7d11ed580 100644 --- a/contracts/config/policies.v2.testnet.json +++ b/contracts/config/policies.v2.testnet.json @@ -8,7 +8,7 @@ }, { "name": "Curation", - "description": "**Court purpose:** \n\n In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", + "description": "**Court purpose:** \n\nIn this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", "summary": "", "requiredSkills": "No particular skills are required.", "court": 2, @@ -16,10 +16,26 @@ }, { "name": "English Language", - "description": "**Court purpose:** \n\n In this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "description": "**Court purpose:** \n\nIn this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", "requiredSkills": "This court requires an advanced level of English. Jurors who are not native English speakers are advised to stake into this court only if they have C1+ level of English.\n\nThe following tests evaluates a C1 level: Cambridge Advanced (CAE), BEC Higher, BULATS score 75+, CLB/CELPIP 8+, CAEL 70+, IELTS level 7, TOEFL 110+, TOEIC score 880+.", "court": 3, "uri": "/ipfs/QmPKVfEdGsERypkHBR4ZhGbkpqEUFSJcddB8xmGJjSqfFv/English-Language-Court-Policy.json" + }, + { + "name": "Corte de Disputas de Consumo y Vecindad", + "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", + "summary": "", + "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", + "court": 4, + "uri": "/ipfs/QmVnqEpQpAeYvdfuPjxnGEvZG7ui7kFz6hhaskpNRgpjk1" + }, + { + "name": "Oracle Court", + "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function.", + "court": 5, + "uri": "/ipfs/QmRNKyST212j2DLLkA7WDBDH65tcGAVxiTkJ54LsZkVat7" } ] diff --git a/contracts/config/policies.v2/Consumo-y-Vecindad.json b/contracts/config/policies.v2/Consumo-y-Vecindad.json new file mode 100644 index 000000000..2a429a3f6 --- /dev/null +++ b/contracts/config/policies.v2/Consumo-y-Vecindad.json @@ -0,0 +1,6 @@ +{ + "name": "Corte de Disputas de Consumo y Vecindad", + "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", + "summary": "", + "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas." +} diff --git a/contracts/config/policies.v2/Oracle-Court-Policy.json b/contracts/config/policies.v2/Oracle-Court-Policy.json new file mode 100644 index 000000000..721ec5650 --- /dev/null +++ b/contracts/config/policies.v2/Oracle-Court-Policy.json @@ -0,0 +1,6 @@ +{ + "name": "Oracle Court", + "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function." +} diff --git a/contracts/package.json b/contracts/package.json index 76d7414c6..0cea3cc17 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -52,6 +52,12 @@ "docgen": "hardhat docgen", "docserve": "scripts/docPreprocess.sh && forge doc --serve", "docbuild": "scripts/docPreprocess.sh && forge doc --build --out dist && scripts/docPostprocess.sh", + "populate:courts:devnet": "hardhat populate:courts --from v2_devnet --network arbitrumSepoliaDevnet", + "populate:courts:testnet": "hardhat populate:courts --from v2_testnet --network arbitrumSepolia", + "populate:courts:mainnetNeo": "hardhat populate:courts --core-type neo --from v2_mainnet_neo --network arbitrum", + "populate:policies:devnet": "hardhat populate:policy-registry --from v2_devnet --network arbitrumSepoliaDevnet", + "populate:policies:testnet": "hardhat populate:policy-registry --from v2_testnet --network arbitrumSepolia", + "populate:policies:mainnetNeo": "hardhat populate:policy-registry --core-type neo --from v2_mainnet_neo --network arbitrum", "release:patch": "scripts/publish.sh patch", "release:minor": "scripts/publish.sh minor", "release:major": "scripts/publish.sh major", diff --git a/contracts/scripts/populateCourts.ts b/contracts/scripts/populateCourts.ts index 4b232f75e..5b26ceb4b 100644 --- a/contracts/scripts/populateCourts.ts +++ b/contracts/scripts/populateCourts.ts @@ -50,6 +50,7 @@ task("populate:courts", "Populates the courts and their parameters") "The source of the policies between v1_mainnet, v1_gnosis, v2_devnet, v2_testnet, v2_mainnet_neo (default: auto depending on the network)", undefined ) + .addOptionalParam("start", "The starting index for the courts to populate (default: 0)", 0, types.int) .addOptionalParam( "maxNumberOfCourts", "The maximum number of courts to populate (default: all)", @@ -61,6 +62,7 @@ task("populate:courts", "Populates the courts and their parameters") "The type of core to use between base, neo, university (default: base)", Cores.BASE.toString() ) + .addFlag("reverse", "Iterates the courts in reverse order, useful to increase minStake in the child courts first") .addFlag("forceV1ParametersToDev", "Use development values for the v1 courts parameters") .setAction(async (taskArgs, hre) => { const { getNamedAccounts, getChainId, ethers, network } = hre; @@ -133,7 +135,7 @@ task("populate:courts", "Populates the courts and their parameters") break; } case Sources.V2_DEVNET: { - courtsV2 = courtsV2ArbitrumDevnet.map(parametersProductionToDev); + courtsV2 = courtsV2ArbitrumDevnet; break; } case Sources.V2_TESTNET: { @@ -148,9 +150,16 @@ task("populate:courts", "Populates the courts and their parameters") throw new Error("Unknown source"); } - const maxNumberOfCourts = taskArgs.maxNumberOfCourts; // set to undefined for all the courts - console.log("Keeping only the first %d courts", maxNumberOfCourts ?? courtsV2.length); - courtsV2 = courtsV2.slice(0, maxNumberOfCourts); + // Warning: the indices are NOT the court IDs, e.g. the forking court is not present in the config so the indices are shifted by 1 + const start = taskArgs.start; + const end = taskArgs.maxNumberOfCourts ? start + taskArgs.maxNumberOfCourts : courtsV2.length; + console.log(`Keeping only the first ${end - start} courts, starting from ${start}`); + courtsV2 = courtsV2.slice(start, end); + + if (taskArgs.reverse) { + console.log("Reversing the order of courts"); + courtsV2.reverse(); + } console.log("courtsV2 = %O", courtsV2); diff --git a/contracts/scripts/populatePolicyRegistry.ts b/contracts/scripts/populatePolicyRegistry.ts index 9967c4863..0961c280b 100644 --- a/contracts/scripts/populatePolicyRegistry.ts +++ b/contracts/scripts/populatePolicyRegistry.ts @@ -27,6 +27,7 @@ task("populate:policy-registry", "Populates the policy registry for each court") "The source of the policies between v1_mainnet, v1_gnosis, v2_devnet, v2_testnet, v2_mainnet_neo (default: auto depending on the network)", undefined ) + .addOptionalParam("start", "The starting index for the courts to populate (default: 0)", 0, types.int) .addOptionalParam( "maxNumberOfCourts", "The maximum number of courts to populate (default: all)", @@ -94,9 +95,11 @@ task("populate:policy-registry", "Populates the policy registry for each court") return; } - const maxNumberOfCourts = taskArgs.maxNumberOfCourts; // set to undefined for all the courts - console.log("Keeping only the first %d courts", maxNumberOfCourts ?? policiesV2.length); - policiesV2 = policiesV2.slice(0, maxNumberOfCourts); + // Warning: the indices are NOT the court IDs, e.g. the forking court is not present in the config so the indices are shifted by 1 + const start = taskArgs.start; + const end = taskArgs.maxNumberOfCourts ? start + taskArgs.maxNumberOfCourts : policiesV2.length; + console.log(`Keeping only the first ${end - start} courts, starting from ${start}`); + policiesV2 = policiesV2.slice(start, end); const policyRegistryDeployment = await deployments.get("PolicyRegistry"); const policyRegistry = (await ethers.getContractAt( From b039784800a8e41bb0383f1a7c7f2cf41734632c Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 24 Jan 2025 18:13:04 +0000 Subject: [PATCH 29/42] feat: styling of policy headers, bullet points, links --- .../pages/Courts/CourtDetails/Description.tsx | 27 +++++++++++++++++++ web/src/styles/global-style.ts | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/web/src/pages/Courts/CourtDetails/Description.tsx b/web/src/pages/Courts/CourtDetails/Description.tsx index 235099dec..87159f784 100644 --- a/web/src/pages/Courts/CourtDetails/Description.tsx +++ b/web/src/pages/Courts/CourtDetails/Description.tsx @@ -21,6 +21,33 @@ const TextContainer = styled.div` p { word-break: break-word; } + + li { + line-height: 1.5em; + margin-top: 0.5em; + } + + h1 { + margin: 16px 0 16px 0; + font-size: 24px; + line-height: 32px; + } + + h2 { + margin: 16px 0 16px 0; + font-size: 20px; + line-height: 24px; + } + + h3 { + margin: 16px 0 16px 0; + font-size: 18px; + line-height: 20px; + } + + a { + font-size: 16px; + } `; const StyledTabs = styled(Tabs)` diff --git a/web/src/styles/global-style.ts b/web/src/styles/global-style.ts index 60e5b7f52..fe8cba478 100644 --- a/web/src/styles/global-style.ts +++ b/web/src/styles/global-style.ts @@ -46,7 +46,7 @@ export const GlobalStyle = createGlobalStyle` h2 { margin: 0 0 16px 0; - font-weight: 400; + font-weight: 600; font-size: 24px; line-height: 32px; color: ${({ theme }) => theme.primaryText}; From ad7924775e10f3ec72608bd422bbec9317562d66 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 24 Jan 2025 21:21:12 +0000 Subject: [PATCH 30/42] refactor: policies JSON schema, cosmetic policy fixes --- contracts/config/policies.v2.devnet.json | 34 ++-- contracts/config/policies.v2.mainnet-neo.json | 188 +++++++++--------- contracts/config/policies.v2.testnet.json | 34 ++-- .../policies.v2/Consumo-y-Vecindad.json | 6 - .../policies.v2/Curation-Court-Policy.json | 6 - .../English-Language-Court-Policy.json | 6 - .../policies.v2/General-Court-Policy.json | 5 - .../policies.v2/Oracle-Court-Policy.json | 6 - contracts/package.json | 1 + contracts/scripts/setPoliciesURIs.sh | 70 +++++++ .../pages/Courts/CourtDetails/Description.tsx | 12 +- 11 files changed, 205 insertions(+), 163 deletions(-) delete mode 100644 contracts/config/policies.v2/Consumo-y-Vecindad.json delete mode 100644 contracts/config/policies.v2/Curation-Court-Policy.json delete mode 100644 contracts/config/policies.v2/English-Language-Court-Policy.json delete mode 100644 contracts/config/policies.v2/General-Court-Policy.json delete mode 100644 contracts/config/policies.v2/Oracle-Court-Policy.json create mode 100755 contracts/scripts/setPoliciesURIs.sh diff --git a/contracts/config/policies.v2.devnet.json b/contracts/config/policies.v2.devnet.json index 7d11ed580..1fd7918e2 100644 --- a/contracts/config/policies.v2.devnet.json +++ b/contracts/config/policies.v2.devnet.json @@ -1,41 +1,41 @@ [ { "name": "General Court", - "description": "**Court Purpose:**\n\nThe General court exists as the top court in the hierarchy. All appeals made in subcourts will make their way to the General Court.", - "summary": "**Guidelines:**\n - All policies of a court also apply to all of its child subcourts.\n - Jurors should cast their vote with a suitable verification.\n - Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n - “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n - Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n - To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n - When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n - Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n - Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise.", + "purpose": "The General court exists as the top court in the hierarchy.\n\nAll appeals made in subcourts will make their way to the General Court.", + "rules": "- All policies of a court also apply to all of its child subcourts.\n - Jurors should cast their vote with a suitable verification.\n - Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n - “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n - Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n - To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n - When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n - Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n - Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise.", "court": 1, - "uri": "/ipfs/QmVtRvx1WHpTcndPyUQos8osKkoFtvZYVLExyjgGxGHP6F/General-Court-Policy.json" + "uri": "/ipfs/QmRDYF4su41noCb447vp9iMicCYfT2a2eXg4wPC3DVR58b" }, { "name": "Curation", - "description": "**Court purpose:** \n\nIn this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", - "summary": "", + "purpose": "In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", + "rules": "", "requiredSkills": "No particular skills are required.", "court": 2, - "uri": "/ipfs/QmTGV621hG2JFsoAiHtd2Y7hWd7msgc3XdsGwqhjzntmKm/Curation-Court-Policy.json" + "uri": "/ipfs/QmPpey7rFbPi25Djcb4ppcGaxR4pojLgpGW2jhUvKxvE5q" }, { "name": "English Language", - "description": "**Court purpose:** \n\nIn this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", + "purpose": "In this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", "requiredSkills": "This court requires an advanced level of English. Jurors who are not native English speakers are advised to stake into this court only if they have C1+ level of English.\n\nThe following tests evaluates a C1 level: Cambridge Advanced (CAE), BEC Higher, BULATS score 75+, CLB/CELPIP 8+, CAEL 70+, IELTS level 7, TOEFL 110+, TOEIC score 880+.", "court": 3, - "uri": "/ipfs/QmPKVfEdGsERypkHBR4ZhGbkpqEUFSJcddB8xmGJjSqfFv/English-Language-Court-Policy.json" + "uri": "/ipfs/QmcMU8hG1UsgEPVykcZFTefeizBux8QPunZAXXTv7KF5B8" }, { "name": "Corte de Disputas de Consumo y Vecindad", - "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", - "summary": "", - "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", + "purpose": "Esta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", + "rules": "", + "requiredSkills": "- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", "court": 4, - "uri": "/ipfs/QmVnqEpQpAeYvdfuPjxnGEvZG7ui7kFz6hhaskpNRgpjk1" + "uri": "/ipfs/QmdfPa7UZxc7iVquQWiTmmRG3n6RSkwN16aXFeK7XyLvjr" }, { - "name": "Oracle Court", - "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", - "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "name": "Oracle", + "purpose": "The Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "rules": "The following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function.", "court": 5, - "uri": "/ipfs/QmRNKyST212j2DLLkA7WDBDH65tcGAVxiTkJ54LsZkVat7" + "uri": "/ipfs/QmT8DAjUbzzEo2e9oPpJSDH2QzswfNeWAsxoDH3zsGrtkH" } ] diff --git a/contracts/config/policies.v2.mainnet-neo.json b/contracts/config/policies.v2.mainnet-neo.json index ff7f04ff5..6e65fe0e9 100644 --- a/contracts/config/policies.v2.mainnet-neo.json +++ b/contracts/config/policies.v2.mainnet-neo.json @@ -1,239 +1,239 @@ [ { "name": "General Court", - "description": "**Court Purpose:**\n\nThe General court exists as the top court in the hierarchy. All appeals made in subcourts will make their way to the General Court.", - "summary": "**Guidelines:**\n - All policies of a court also apply to all of its child subcourts.\n - Jurors should cast their vote with a suitable verification.\n - Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n - “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n - Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n - To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n - When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n - Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n - Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise.", + "purpose": "The General court exists as the top court in the hierarchy.\n\nAll appeals made in subcourts will make their way to the General Court.", + "rules": "- All policies of a court also apply to all of its child subcourts.\n- Jurors should cast their vote with a suitable verification.\n- Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n- “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n- Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n- Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n- To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n- When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n- Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n- Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise.", "court": 1, - "uri": "/ipfs/QmVtRvx1WHpTcndPyUQos8osKkoFtvZYVLExyjgGxGHP6F/General-Court-Policy.json" + "uri": "/ipfs/QmRwmJAF8NK1r3fAS8dHofbTKsuhWSd3LruzkjrpNNBprC" }, { "name": "Blockchain", - "description": "**Court Purpose:**\n\nThis is the blockchain community subcourt. Disputes in this subcourt should be those that require that jurors have an understanding of the broad blockchain ecosystem. Cases in this court may come from varying aspects of the ecosystem and could also be from lower courts that have been appealed. For example, a case in the Token Curated Registry could arrive here on appeal.\n", - "summary": "", + "purpose": "This is the blockchain community subcourt. Disputes in this subcourt should be those that require that jurors have an understanding of the broad blockchain ecosystem. Cases in this court may come from varying aspects of the ecosystem and could also be from lower courts that have been appealed. For example, a case in the Token Curated Registry could arrive here on appeal.", + "rules": "", "court": 2, - "uri": "/ipfs/QmYMdCkb7WULmiK6aQrgsayGG3VYisQwsHSLC3TLkzEHCm" + "uri": "/ipfs/QmX4DuuKAHX7rqMcnmYLHHEWvW93bdJ2zNUVBmNRX4kKQK" }, { "name": "Non-Technical", - "description": "**Court Purpose:**\n\nThis subcourt is for small non-technical blockchain disputes. It is used for disputes on challenged tokens from Kleros Token² Curated Registry Dapp, Cryptoasset Transfer and Exchange Listing agreement escrow disputes. This can include:\n\n- **Token² Curated Registry**: A curated list of verified tokens submitted by users. This includes, logo, token name, contract address and ticker. \n\n- **Cryptoasset Transfer Escrow**: This escrow can be used by users to safely and securely transfer cryptoassets between two parties, even if one cryptoasset is on a chain other than Ethereum. Funds are locked in a smart contract until the other party has complied with the agreement or a dispute is brought. An example use case could be transferring ETH for BTC P2P without knowledge of the other party. You deposit ETH into the escrow, the other party sends BTC and you release ETH.\n\n- **Exchange Listing Agreement Escrow**: This escrow can be used to delegate listing of tokens to token listing agents. A reward is paid to the agent if the token is appropriately listed on the agreed upon exchange.\n\n**Example:**\n\n- Someone submits the PNK token with the address “0x87c260900c391559fd2816c9fbf078de37e2f520”. Someone challenges the listing as incorrect as the real PNK address is “0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d”.\n\n- Parties make a contract to exchange 1 BTC for 30 ETH. After the deadline agreed in the contract, the address of the BTC buyer still hasn’t been credited.\n\n- Contractor agreed to list clients token or coin in both USD and DAI pairings but did not deliver USD.", - "summary": "**Policies:** \n\n- In escrow disputes involving privacy coins where “view key’s” are needed, those should be provided as evidence before the end of the evidence period. ", + "purpose": "This subcourt is for small non-technical blockchain disputes. It is used for disputes on challenged tokens from Kleros Token² Curated Registry Dapp, Cryptoasset Transfer and Exchange Listing agreement escrow disputes. This can include:\n- **Token² Curated Registry**: A curated list of verified tokens submitted by users. This includes, logo, token name, contract address and ticker. \n- **Cryptoasset Transfer Escrow**: This escrow can be used by users to safely and securely transfer cryptoassets between two parties, even if one cryptoasset is on a chain other than Ethereum. Funds are locked in a smart contract until the other party has complied with the agreement or a dispute is brought. An example use case could be transferring ETH for BTC P2P without knowledge of the other party. You deposit ETH into the escrow, the other party sends BTC and you release ETH.\n- **Exchange Listing Agreement Escrow**: This escrow can be used to delegate listing of tokens to token listing agents. A reward is paid to the agent if the token is appropriately listed on the agreed upon exchange.\n### Example\n- Someone submits the PNK token with the address “0x87c260900c391559fd2816c9fbf078de37e2f520”. Someone challenges the listing as incorrect as the real PNK address is “0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d”.\n- Parties make a contract to exchange 1 BTC for 30 ETH. After the deadline agreed in the contract, the address of the BTC buyer still hasn’t been credited.\n- Contractor agreed to list clients token or coin in both USD and DAI pairings but did not deliver USD.", + "rules": "In escrow disputes involving privacy coins where “view key’s” are needed, those should be provided as evidence before the end of the evidence period. ", "requiredSkills": "Jurors do not need a deep blockchain technical knowledge or coding skills but do need the ability to read blockchain explorers, look at cryptoassets listed on exchanges, understand how to verify a transaction and cross reference on-chain data. ", "court": 3, - "uri": "/ipfs/QmdJYHubLGQCt2GxpJch2riSYVxZzDC4cBg2mNPXuiY6rX" + "uri": "/ipfs/QmPVEjGaZ9f1sWmqjbqvgcmPWM2686VDgccZce2ZwhAtiD" }, { "name": "Token Listing", - "description": "**Court Purpose:**\n\nThis court serves as the final validation for token listing for verified projects listing on the Ethfinex Exchange using Kleros’ Token Curated List Dapp.\nThis is a high level, high stake court requiring deep blockchain knowledge, legal experience and / or a knowledge of exchange listings in general. Jurors are required to stake a large amount of PNK and should only do so if they are confident in the above capabilities.", - "summary": "[Ethfinex Court Policy](https://cdn.kleros.link/ipfs/QmVzwEBpGsbFY3UgyjA3SxgGXx3r5gFGynNpaoXkp6jenu/Ethfinex%20Court%20Policy.pdf)", + "purpose": "This court serves as the final validation for token listing for verified projects listing on the Ethfinex Exchange using Kleros’ Token Curated List Dapp.\nThis is a high level, high stake court requiring deep blockchain knowledge, legal experience and / or a knowledge of exchange listings in general. Jurors are required to stake a large amount of PNK and should only do so if they are confident in the above capabilities.", + "rules": "[Ethfinex Court Policy](https://cdn.kleros.link/ipfs/QmVzwEBpGsbFY3UgyjA3SxgGXx3r5gFGynNpaoXkp6jenu/Ethfinex%20Court%20Policy.pdf)", "court": 4, - "uri": "/ipfs/QmeyojE13mcQtWqQQddYypafDRChVND8z6dcHLbaarmbbx" + "uri": "/ipfs/QmU4AgUKUD7oXkmu4FM8o2mXEJXnnV4Kv4u1CgAWriTrgq" }, { "name": "Technical", - "description": "**Court Purpose:**\n\nThis court serves to arbitrate blockchain disputes of a technical nature. This can include:\n\n- Verifying that a smart contract meets a defined standard. \n\n- Verifying that a proposed contract call is the technical translation of a decision taken by governance.\n\n**Example:**\n\n- A dispute on whether or not a token should be given a badge indicating that it satisfies ERC20. \n\n- A dispute on whether or not a proposed Kleros governor call matches the decision which has been voted through governance.", - "summary": "**Policies:** \n\n- Disputes in this subcourt should only be of technical nature. ", + "purpose": "This court serves to arbitrate blockchain disputes of a technical nature. This can include:\n- Verifying that a smart contract meets a defined standard.\n- Verifying that a proposed contract call is the technical translation of a decision taken by governance.\n### Example\n- A dispute on whether or not a token should be given a badge indicating that it satisfies ERC20.\n- A dispute on whether or not a proposed Kleros governor call matches the decision which has been voted through governance.", + "rules": "- Disputes in this subcourt should only be of technical nature. ", "requiredSkills": "A high understanding of blockchain technology, smart contract, solidity language and Ethereum ABI is required.", "court": 5, - "uri": "/ipfs/QmcBjGYfmKmkpYc8HYkaiBa9ot2eoWAa2Mhfef7i7QKd5H" + "uri": "/ipfs/QmX43E594Prj7KcaCfvPBpcg6soVrnxJWbYmfUtxM1tGwk" }, { "name": "Marketing Services", - "description": "**Court Purpose:**\n\nIn this court, jurors will solve disputes on quality of paid marketing services including but not exclusive to sponsored articles, social media promotion and PR writing.\n\n**Example**\n\n- Marketing company promised to publish article on Cointelegraph and subsequently list on Google news, neither of these things happened.", - "summary": "**Policies:** \n\n- It is the responsibility of the marketing contractor to prove that the service has been delivered. This should be done by providing evidence.", + "purpose": "In this court, jurors will solve disputes on quality of paid marketing services including but not exclusive to sponsored articles, social media promotion and PR writing.\n### Example\nMarketing company promised to publish article on Cointelegraph and subsequently list on Google news, neither of these things happened.", + "rules": "It is the responsibility of the marketing contractor to prove that the service has been delivered. This should be done by providing evidence.", "requiredSkills": "A high level of reading and writing comprehension, data corroboration and web search.", "court": 6, - "uri": "/ipfs/QmbSwJ4acdQP9EF6DfDU6czaG8ePha3eyvcSpPgAR8tPZ4" + "uri": "/ipfs/QmSrfZRXnfeseSvzTeWXL1dKcVyGnPYvuoQD7JQoRS6GSr" }, { "name": "English Language", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n###Example\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "This subcourt requires an advanced level of English. Jurors who are not native English speakers are advised to stake into this court only if they have C1+ level of English.The following tests evaluates a C1 level: Cambridge Advanced (CAE), BEC Higher, BULATS score 75+, CLB/CELPIP 8+, CAEL 70+, IELTS level 7, TOEFL 110+, TOEIC score 880+.", "court": 7, - "uri": "/ipfs/QmSn2RJX7a4BQ8rDtfvKLjKQSC3eHWjayPTSkFo3QMbjBx" + "uri": "/ipfs/Qme3QwJs36fcqiC5KUMGwSubhfoUkLBjYBBs1cAabjQoU1" }, { "name": "Video Production", - "description": "**Court Purpose:**\n\nThis court is for disputes on video production created through agreements in the Kleros escrow. This included editing quality, resolution and final deliverable format.\n\n**Example:**\n\n- Freelancer did not complete video project as agreed in contract. Agreement stated video should be of 1min 30 seconds long but was only 59 seconds.", - "summary": "**Policies:**\n\n- If the video is longer than 1h, parties in the dispute should draw attention to certain sections of the video that do not meet the requirements of the agreement.", + "purpose": "This court is for disputes on video production created through agreements in the Kleros escrow. This included editing quality, resolution and final deliverable format.\n### Example\nFreelancer did not complete video project as agreed in contract. Agreement stated video should be of 1min 30 seconds long but was only 59 seconds.", + "rules": "- If the video is longer than 1h, parties in the dispute should draw attention to certain sections of the video that do not meet the requirements of the agreement.", "requiredSkills": "Knowledge of video editing programs / encoding standards and editing procedures. Ability to check resolutions, durations and visual branding on platforms such as YouTube, Vimeo etc.", "court": 8, - "uri": "/ipfs/QmXvtokEk3qPiB2WPXXUpd4xCoAr5xeceS1n4BHHqNpP7p" + "uri": "/ipfs/QmWV29MMfPx9qh5YCevZdWF5Bm7tBCMCfL15H4Gs2SHSMc" }, { "name": "Onboarding", - "description": "**Court Purpose:**\n\n- Allow new jurors to get a feel of Kleros by solving a variety of small disputes.\n- Allow projects considering Kleros use to have some disputes solved with Kleros in order to compare Kleros results with other methods.", - "summary": "**Policies:** \n\n- Disputes should be relatively simple. They should require less than 1 hour to solve.", + "purpose": "- Allow new jurors to get a feel of Kleros by solving a variety of small disputes.\n- Allow projects considering Kleros use to have some disputes solved with Kleros in order to compare Kleros results with other methods.", + "rules": "Disputes should be relatively simple. They should require less than 1 hour to solve.", "requiredSkills": "No particular skills are required.", "court": 9, - "uri": "/ipfs/QmbC7uhDEC33V8zyp8u6xozuD3GwtMp4Eaw25EzscEJk3R/Bce1VQaKwHGhMXxqgsmzJLH79ngeP4c57hGBeQQmSCZmPJcgrq4jBj3eFuMsgXuJhfYCXbARyNDx8oNvgusd9pDLjt" + "uri": "/ipfs/QmT92EfehJpUgbvDSEM4b8nLJ4Y8rrAig6g9T41iCYyApx" }, { "name": "Curation", - "description": "**Court purpose:** \n\n In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", - "summary": "", + "purpose": "In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", + "rules": "", "requiredSkills": "No particular skills are required.", "court": 10, - "uri": "/ipfs/QmWcf4mgnPyxUVbRMKmNjx9pzk3scQRg8bVbNjDdfgh2Nq" + "uri": "/ipfs/QmNRutLfBNXrFpVACnCiGhfm837pEMTUpokgpArjg9oP4n" }, { "name": "Data Analysis", - "description": "", - "summary": "", + "purpose": "", + "rules": "", "requiredSkills": "Jurors should be comfortable dealing with data sets, particularly in being able to understand the context of the data from the structure of the data set, and in estimating what percentage of entries are unusable/would need to be cleaned.", "court": 11, - "uri": "/ipfs/Qmb3r65GXcoWfkpb6m3mnzgCyTyz7dk59UaY4iW6eTKkqJ" + "uri": "/ipfs/QmVRzM6ZFSegnDzX9zN5oUqXut75j3pdRdqdcb86ZoWXac" }, { "name": "Statistical Modeling", - "description": "", - "summary": "", + "purpose": "", + "rules": "", "requiredSkills": "Jurors should be comfortable dealing with data sets. Furthermore, jurors should have a background in statistics equivalent to (at minimum) a university statistics course, and they should be able to analyze the assumptions taken in the creation of statistical models.", "court": 12, - "uri": "/ipfs/QmSu7HxnTmQQz23EPTAMv7oF1NsBM752mEEytCDrgdoAUx" + "uri": "/ipfs/QmZSZfQDkpkz5MXLThmV2ZkLhwjFAXWe9XxvSEhr2M8rcQ" }, { "name": "Curation (Medium)", - "description": "**Court purpose:** \n\n In this court, jurors will solve tasks of “medium difficulty” related to curation or content moderation. Here requirements to be analyzed can be more complicated than those of the micro-tasks in the Curation court; however, much higher effort cases should still be placed in other courts, but might arrive in this court upon appeal.", - "summary": "", + "purpose": "In this court, jurors will solve tasks of “medium difficulty” related to curation or content moderation. Here requirements to be analyzed can be more complicated than those of the micro-tasks in the Curation court; however, much higher effort cases should still be placed in other courts, but might arrive in this court upon appeal.", + "rules": "", "requiredSkills": "No particular skills are required.", "court": 13, - "uri": "/ipfs/QmeGQ5pq7eDcS3NmKXyPsRFLEXd9pJe3MHKdDviy3buDce" + "uri": "/ipfs/QmeMp1yVg385hPNjW6Xz6GL9noUhHpVmyFvFCpDKosTWEi" }, { "name": "Spanish-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Spanish. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Spanish for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English, or Mexican/Argentinian/European Spanish.", "court": 14, - "uri": "/ipfs/QmY79fya7FQAzvhjzS6S5w9N8TkXZTQ5TSajqdC26VVK6L" + "uri": "/ipfs/Qmf1cCBh5iWVWEZoKbwCxVymFefAbmvoFbUtCEu8jzDUXf" }, { "name": "French-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and French. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and French for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English, or European/Québécois French.", "court": 15, - "uri": "/ipfs/QmQtCrG1EQzLiboYtQ15oWYstPrBUgftBUHmhUSZGk3jWc" + "uri": "/ipfs/QmaWhoi2JFXqbXsMjKfqaNENknXjzuaJeUEU3YdTKwiuj9" }, { "name": "Portuguese-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Portuguese. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Portuguese for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English, or Brazilian/European Portuguese.", "court": 16, - "uri": "/ipfs/QmYdR9v8LzYnw9pT4ZCcWyoivFMPNyQcBFXgCW3PZRSMCF" + "uri": "/ipfs/QmVseoDMTcexMBSXDSJc75LimLZmhbKYDe27xwK8xtBqs1" }, { "name": "German-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and German. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and German for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English.", "court": 17, - "uri": "/ipfs/QmcqBRYin9Ug4YX7ysGf65xDjAQecuRzWp7nSucGvBcpwP" + "uri": "/ipfs/QmZ4yfbLnN3FyFfTyPeZNw2M1B4UbWu75fCVaVEuNkgqJR" }, { "name": "Russian-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Russian. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Russian for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English.", "court": 18, - "uri": "/ipfs/QmXoSvQJCW4HVjB6vreL8YwXj2HHJSpwNzroMkojos7p6c" + "uri": "/ipfs/QmbcQyKHUMXzJv1T7deDVuUKa9W4ZzkJiCthMkceRjqWTu" }, { "name": "Korean-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Korean. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Korean for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English.", "court": 19, - "uri": "/ipfs/QmUJGjgDCX9Bsn5fL6ZAZdXRLke3Pbdhwo312hJSewsKwg" + "uri": "/ipfs/QmSFSSohm7r3inSxCuNLAkoQpe2jnQJd5eXzsFeYWGcAQ8" }, { "name": "Japanese-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Japanese. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Japanese for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English.", "court": 20, - "uri": "/ipfs/QmWQ5NCSjZM8NK3quv72wcD7nbs9MfMxWajYnUgrZRAWch" + "uri": "/ipfs/QmS1rS5jByBSM1frt8fhD33X4Mb1JqcgMCBpTtKv5ciHBi" }, { "name": "Turkish-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Turkish. While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Turkish for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English.", "court": 21, - "uri": "/ipfs/QmNSpBnACohhHwSpqg5nG8ZVxED2B4pMiputfZqZdbZvuc" + "uri": "/ipfs/QmWjPb7a3XUQjN5eFHRPiBB1KkVFY9fes2LxGAN41PnUNW" }, { "name": "Chinese-English Translation", - "description": "**Court Purpose:**\n\nIn this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example:**\n\n- Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", + "purpose": "In this subcourt, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n### Example\nContractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out to specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n- All variations of English (UK, US, Australia, etc) are to accepted unless a target audience is specifically mentioned in the contract. ", "requiredSkills": "Jurors in this court should have a strong command of both English and Chinese (written in simplified characters). While jurors do not necessarily have to have the skills of a professional translator themselves, they should be able to review texts translated between English and Chinese for the quality of their translation. Particularly, when presented with passages that are flagged by challengers as potentially erroneous or inadequate and arguments given by the translator and the challenger for why the passage should or should not be considered acceptable, jurors should be able to make a determination on the quality of the passage. Jurors should be capable of these tasks even when the text is being translated to or from regional variations of these languages, such as US/British English.", "court": 22, - "uri": "/ipfs/QmbuTx2dcWGSqGo7ykMhMDbvs6oM1AcbC8LvbfAQohup25" + "uri": "/ipfs/QmeDe5dsrofxU8bHU1TpwkbJrFdHCseFkPL74Pg47TbJ7i" }, { "name": "Corte General en Español", - "description": "**Propósito de la Corte:**\n\nLa Corte General en Español sirve como corte de uso múltiple para todo tipo de disputas que requieran un alto nivel de conocimiento del español para ser evaluadas. Los casos resueltos por cortes más especializadas que requieran conocimientos de español además de otras habilidades pueden ser revisados en esta corte tras el proceso de apelación.", - "summary": "**Políticas:**\n\nAdemás del requisito de que los jurados tengan un nivel suficiente de español, esta corte debe tener la misma política que la Corte General de Kleros.", - "requiredSkills": "**Habilidades Requeridas:**\n\nEste tribunal requiere un nivel avanzado de español. Los miembros del jurado deben poder leer la evidencia y proporcionar una justificación adecuada en español sobre una amplia gama de temas.", + "purpose": "La Corte General en Español sirve como corte de uso múltiple para todo tipo de disputas que requieran un alto nivel de conocimiento del español para ser evaluadas. Los casos resueltos por cortes más especializadas que requieran conocimientos de español además de otras habilidades pueden ser revisados en esta corte tras el proceso de apelación.", + "rules": "Además del requisito de que los jurados tengan un nivel suficiente de español, esta corte debe tener la misma política que la Corte General de Kleros.", + "requiredSkills": "Este tribunal requiere un nivel avanzado de español. Los miembros del jurado deben poder leer la evidencia y proporcionar una justificación adecuada en español sobre una amplia gama de temas.", "court": 23, - "uri": "/ipfs/QmRPz626unSwc7fxo3ikoZzXmCpVm5EHfiSz2bfErCsHti" + "uri": "/ipfs/QmcaMbPgKAAvc67URzbq1yegnCANPRSNSmLQ7GwsyYNTCe" }, { "name": "Humanity Court", - "description": "**Court Purpose:**\n\nIn this court jurors will judge disputes related to establishing Sybil resistant lists of unique human identities, particularly for the Proof of Humanity protocol.\n\n", - "summary": "", + "purpose": "In this court jurors will judge disputes related to establishing Sybil resistant lists of unique human identities, particularly for the Proof of Humanity protocol.\n\n", + "rules": "", "requiredSkills": "Jurors should be capable of reasonably evaluating whether a proposed submission consisting of photo and video evidence corresponds to a unique human being, eventually making use of supplementary information that might be provided as evidence by relevant parties.", "court": 24, - "uri": "/ipfs/QmQKEJbyw89Qh5AurQ4kvidCSr32ihQUdAQZ646cPkJV34" + "uri": "/ipfs/QmXAVumYfMmezMQSbhYn33iCFxwqLguRztz7HcJaLnX1Z4" }, { "name": "xDai Development Court", - "description": "**Court purpose:** \n\n In this court, jurors will solve disputes involving the respect of specifications given by the client.", - "summary": "**Example** \n\n Developper does not respect indentation, does not name variables explicitly or has not made a clear file structure. In such cases, jurors should refuse the proposal made by the developer.", - "requiredSkills": "This court requires a good level of programmation. Jurors who are not intermediate developers are advised to stake into this court only if they have some basics of low-level programming languages, ​​algorithmic and knowledge of good practices of development.", + "purpose": "In this court, jurors will solve disputes involving the respect of specifications given by the client.", + "rules": "### Example\nDeveloper does not respect indentation, does not name variables explicitly or has not made a clear file structure. In such cases, jurors should refuse the proposal made by the developer.", + "requiredSkills": "This court requires a good level of familiarity with programming. Jurors who are not intermediate developers are advised to stake into this court only if they have some basics of low-level programming languages, ​​algorithmic and knowledge of good practices of development.", "court": 25, - "uri": "/ipfs/QmbgUL2iv9XH3jui7xdLBXp2Hqe4VqGnNkK7PnAorJ8XQa/xDai-Development-Court-Policy.json" + "uri": "/ipfs/QmZ8gkBgHsomDpdJG6duJP2VN7u177NT7nsx2bjaLzJknN" }, { "name": "xDai Solidity Court", - "description": "**Court purpose:** \n\n If the disputed code is of significant size (> 500 code lines), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.", - "summary": "", + "purpose": "", + "rules": "If the disputed code is of significant size (> 500 code lines), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.", "requiredSkills": "This court requires a good level of solidity. Jurors who are not solidity intermediate developers are advised to stake into this court only if they also know how to make relatively simple contracts, know the main solidity hacks and can compute the complexity of simple functions.", "court": 26, - "uri": "/ipfs/QmQbyk1qnD4e4MQrwSr6a21w2t82YJEMxU3F7QTYKkxuNS/xDai-Solidity-Court-Policy.json" + "uri": "/ipfs/QmNfeN8JW7GyawEQvsAuRAhm6aw5RpgJzYDKTPQGyPqvKA" }, { "name": "xDai Javascript Court", - "description": "**Court purpose:** \n\n If the disputed code is of significant size (> 700 code lines), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.", - "summary": "", + "purpose": "", + "rules": "If the disputed code is of significant size (> 700 code lines), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.", "requiredSkills": "This court requires a good level of javascript. Jurors who are not javascript intermediate developers are advised to stake into this court only if they know the main frameworks/libraries (ExpressJs, React, EthersJs…) and be comfortable with testing, APIs or languages to interact with databases.", "court": 27, - "uri": "/ipfs/Qme15AUfpvLX3iwEtqswe26PQHMmKnF4eWGywBPqbkdqcD/xDai-Javascript-Court-Policy.json" + "uri": "/ipfs/QmYdsdvqbbHKus5Y2djAkvTrtK8piPSmeM3WfPB9ntKk4K" }, { "name": "Corte de Curación en Español", - "description": "**Propósito de la Corte** \n\n En esta corte, los miembros del jurado resuelven microtareas relacionadas con la curación o la moderación de contenido, como para redes sociales, cuando los requisitos son relativamente sencillos. Los casos de mayor esfuerzo, que requieren la aplicación de reglas con mayores matices, deben presentarse en otras cortes, pero pueden llegar a este tribunal en caso de apelación.", - "summary": "", - "requiredSkills": "**Habilidades Requeridas:** \n\n Este tribunal requiere un nivel avanzado de español. Los miembros del jurado deben poder leer la evidencia y proporcionar una justificación adecuada en español sobre una amplia gama de temas.", + "purpose": "En esta corte, los miembros del jurado resuelven microtareas relacionadas con la curación o la moderación de contenido, como para redes sociales, cuando los requisitos son relativamente sencillos. Los casos de mayor esfuerzo, que requieren la aplicación de reglas con mayores matices, deben presentarse en otras cortes, pero pueden llegar a este tribunal en caso de apelación.", + "rules": "", + "requiredSkills": "Este tribunal requiere un nivel avanzado de español. Los miembros del jurado deben poder leer la evidencia y proporcionar una justificación adecuada en español sobre una amplia gama de temas.", "court": 28, - "uri": "/ipfs/QmQeHpuaL9RViwKnPNRMCAbPakdPSDefBmhPpMAi45vRLS/xDai-Spanish-Curation-Court-Policy.json" + "uri": "/ipfs/QmWGwXsDrFzb26pXM7dvAHSLjvM4p1DUuDT3FtVX7J1jtt" }, { "name": "Corte de Disputas de Consumo y Vecindad", - "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", - "summary": "", - "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", + "purpose": "Esta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", + "rules": "", + "requiredSkills": "- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", "court": 29, - "uri": "/ipfs/QmVnqEpQpAeYvdfuPjxnGEvZG7ui7kFz6hhaskpNRgpjk1" + "uri": "/ipfs/Qmczrn2DgdKGnacdvKRYwCk7JkeyTCokdqQycWdetYrxGC" }, { "name": "Oracle Court", - "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", - "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "purpose": "The Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "rules": "The following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function.", "court": 30, - "uri": "/ipfs/QmRNKyST212j2DLLkA7WDBDH65tcGAVxiTkJ54LsZkVat7" + "uri": "/ipfs/QmVFKNM1F3YnH2DVFh1Xd6epL9Asum2xBm9kGUQeXypAN5" } ] diff --git a/contracts/config/policies.v2.testnet.json b/contracts/config/policies.v2.testnet.json index 7d11ed580..1fd7918e2 100644 --- a/contracts/config/policies.v2.testnet.json +++ b/contracts/config/policies.v2.testnet.json @@ -1,41 +1,41 @@ [ { "name": "General Court", - "description": "**Court Purpose:**\n\nThe General court exists as the top court in the hierarchy. All appeals made in subcourts will make their way to the General Court.", - "summary": "**Guidelines:**\n - All policies of a court also apply to all of its child subcourts.\n - Jurors should cast their vote with a suitable verification.\n - Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n - “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n - Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n - To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n - When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n - Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n - Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise.", + "purpose": "The General court exists as the top court in the hierarchy.\n\nAll appeals made in subcourts will make their way to the General Court.", + "rules": "- All policies of a court also apply to all of its child subcourts.\n - Jurors should cast their vote with a suitable verification.\n - Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n - “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n - Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n - To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n - When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n - Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n - Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise.", "court": 1, - "uri": "/ipfs/QmVtRvx1WHpTcndPyUQos8osKkoFtvZYVLExyjgGxGHP6F/General-Court-Policy.json" + "uri": "/ipfs/QmRDYF4su41noCb447vp9iMicCYfT2a2eXg4wPC3DVR58b" }, { "name": "Curation", - "description": "**Court purpose:** \n\nIn this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", - "summary": "", + "purpose": "In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", + "rules": "", "requiredSkills": "No particular skills are required.", "court": 2, - "uri": "/ipfs/QmTGV621hG2JFsoAiHtd2Y7hWd7msgc3XdsGwqhjzntmKm/Curation-Court-Policy.json" + "uri": "/ipfs/QmPpey7rFbPi25Djcb4ppcGaxR4pojLgpGW2jhUvKxvE5q" }, { "name": "English Language", - "description": "**Court purpose:** \n\nIn this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", + "purpose": "In this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", + "rules": "- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", "requiredSkills": "This court requires an advanced level of English. Jurors who are not native English speakers are advised to stake into this court only if they have C1+ level of English.\n\nThe following tests evaluates a C1 level: Cambridge Advanced (CAE), BEC Higher, BULATS score 75+, CLB/CELPIP 8+, CAEL 70+, IELTS level 7, TOEFL 110+, TOEIC score 880+.", "court": 3, - "uri": "/ipfs/QmPKVfEdGsERypkHBR4ZhGbkpqEUFSJcddB8xmGJjSqfFv/English-Language-Court-Policy.json" + "uri": "/ipfs/QmcMU8hG1UsgEPVykcZFTefeizBux8QPunZAXXTv7KF5B8" }, { "name": "Corte de Disputas de Consumo y Vecindad", - "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", - "summary": "", - "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", + "purpose": "Esta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", + "rules": "", + "requiredSkills": "- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas.", "court": 4, - "uri": "/ipfs/QmVnqEpQpAeYvdfuPjxnGEvZG7ui7kFz6hhaskpNRgpjk1" + "uri": "/ipfs/QmdfPa7UZxc7iVquQWiTmmRG3n6RSkwN16aXFeK7XyLvjr" }, { - "name": "Oracle Court", - "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", - "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", + "name": "Oracle", + "purpose": "The Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", + "rules": "The following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function.", "court": 5, - "uri": "/ipfs/QmRNKyST212j2DLLkA7WDBDH65tcGAVxiTkJ54LsZkVat7" + "uri": "/ipfs/QmT8DAjUbzzEo2e9oPpJSDH2QzswfNeWAsxoDH3zsGrtkH" } ] diff --git a/contracts/config/policies.v2/Consumo-y-Vecindad.json b/contracts/config/policies.v2/Consumo-y-Vecindad.json deleted file mode 100644 index 2a429a3f6..000000000 --- a/contracts/config/policies.v2/Consumo-y-Vecindad.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Corte de Disputas de Consumo y Vecindad", - "description": "**Propósito de la Corte** \n\nEsta corte está destinada a resolver una amplia variedad de disputas de complejidad baja a moderada, principalmente de carácter civil y comercial. La misma actúa como tribunal subsidiario para los casos en los que no exista otra corte más especializada o aplicable. Su alcance incluye, pero no se limita a:\n- Conflictos contractuales.\n- Reclamos por daños y perjuicios.\n- Reclamos de defensa del consumidor entre usuarios y empresas.", - "summary": "", - "requiredSkills": "**Habilidades Requeridas:** \n\n- Familiaridad con los [derechos básicos del consumidor](https://buenosaires.gob.ar/principales-derechos-de-los-consumidores).\n- Comprensión de las prácticas comunes en entornos digitales y descentralizados.\n- Capacidad para evaluar de manera crítica y objetiva la autenticidad y relevancia de las pruebas presentadas." -} diff --git a/contracts/config/policies.v2/Curation-Court-Policy.json b/contracts/config/policies.v2/Curation-Court-Policy.json deleted file mode 100644 index acfc20e92..000000000 --- a/contracts/config/policies.v2/Curation-Court-Policy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Curation", - "description": "**Court purpose:** \n\n In this court, jurors will solve micro-tasks related to curation or content moderation, such as for social media, when requirements for inclusion are relatively straightforward. Higher effort cases, requiring application of more nuanced rules should be placed in other courts but might arrive in this court upon appeal.", - "summary": "", - "requiredSkills": "No particular skills are required." -} diff --git a/contracts/config/policies.v2/English-Language-Court-Policy.json b/contracts/config/policies.v2/English-Language-Court-Policy.json deleted file mode 100644 index 069bed596..000000000 --- a/contracts/config/policies.v2/English-Language-Court-Policy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "English Language", - "description": "**Court purpose:** \n\n In this court, jurors will solve disputes involving quality of written content. This includes grammar and text logic.\n\n**Example**\n\n - Contractor utilizes poor grammar in a sponsored article which doesn’t meet the standards as agreed in the contract.", - "summary": "**Policies:**\n\n- If the disputed content is of significant size (> 10 000 words), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.\n\n- All variations of English (UK, US, Australia, etc) are to be accepted unless a target audience is specifically mentioned in the contract.", - "requiredSkills": "This court requires an advanced level of English. Jurors who are not native English speakers are advised to stake into this court only if they have C1+ level of English.\n\nThe following tests evaluates a C1 level: Cambridge Advanced (CAE), BEC Higher, BULATS score 75+, CLB/CELPIP 8+, CAEL 70+, IELTS level 7, TOEFL 110+, TOEIC score 880+." -} diff --git a/contracts/config/policies.v2/General-Court-Policy.json b/contracts/config/policies.v2/General-Court-Policy.json deleted file mode 100644 index 8c3cf8ef5..000000000 --- a/contracts/config/policies.v2/General-Court-Policy.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "General Court", - "description": "**Court Purpose:**\n\nThe General court exists as the top court in the hierarchy. All appeals made in subcourts will make their way to the General Court.", - "summary": "**Guidelines:**\n - All policies of a court also apply to all of its child subcourts.\n - Jurors should cast their vote with a suitable verification.\n - Jurors should not rule in favor of a side who have engaged in immoral activities (example: rule reject on “revenge porn” images even if they would otherwise fit into the category).\n - “Refuse to arbitrate” should be used for disputes where both sides of the dispute have engaged in activities which are immoral (ex: refuse to rule on an assassination market dispute).\n Immoral activities include: Murder, slavery, rape, violence, theft and perjury.\n - Rulings should be made based on the “state of the world” at the time a dispute was created. (Ex: in a dispute concerning membership of a smart contract on a curated list of “bug free” contracts, jurors should not take into account changes made to the contract after the dispute is raised.) In particular, jurors should base their rulings on court policies and arbitrable application primary documents as they exist at the time of the creation of the dispute, disregarding later modifications.\n - To ensure fairness to jurors who vote at different times within a voting period, jurors should disregard any evidence that is both 1) submitted after the end of the evidence period of the initial round of a dispute AND 2) cannot be reasonably considered to have been readily, publicly available to jurors. Jurors may, however, consider arguments that are submitted later that are based upon existing evidence and/or information which a juror considering the case during the evidence period of the initial round could reasonably have been expected to find themselves. (Ex: a party submits a new photo of a damaged product in an insurance case after the evidence period; this photo should not be considered by jurors. Ex: in a dispute over whether a token satisfies the criteria of a curated list of ERC20 tokens, an argument that reminds jurors of a definitional element of the ERC20 standard is submitted; this is publicly available and can be considered by jurors. Ex: in a dispute over whether a token satisfies a decentralization criterion for an exchange listing, an argument that invokes the distribution of tokens over different Ethereum addresses, as publicly available from sites such as Etherscan, can be considered by jurors.)\n - When considering an appeal of a case that has originated in a lower court, jurors should consider whether 1) evaluating the case requires specialized skills which jurors in the appellate court cannot be expected to have (ex: evaluating the quality of an English to Korean translation when knowledge of Korean is not a requirement of the appellate court) and 2) whether there is evidence that an attack was performed against this case in the lower court (ex: bribes, p+epsilon attacks, 51% attacks, etc). If there is no evidence of an attack AND appellate court jurors cannot be reasonably expected to have the required skills to independently evaluate the case, jurors should vote to uphold the lower court ruling. Evidence related to the presence of attacks on Kleros should be considered by jurors even if it would otherwise violate the above points on evidence admissibility.\n - Jurors should attempt to interpret disputes according to the “spirit of the dispute” unless the arbitrable contract or the policies of the subcourt state otherwise.\n - Jurors should interpret disputes without assuming the existence of gods, spirits or other supernatural beings unless the arbitrable contract or the policies of the subcourt state otherwise." -} diff --git a/contracts/config/policies.v2/Oracle-Court-Policy.json b/contracts/config/policies.v2/Oracle-Court-Policy.json deleted file mode 100644 index 721ec5650..000000000 --- a/contracts/config/policies.v2/Oracle-Court-Policy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Oracle Court", - "description": "## Court Purpose\nThe Oracle Court is designed to resolve disputes related to reporting real-world events, including but not limited to those originating from prediction markets.", - "summary": "## Policy\nThe following rules are subsidiary and will apply only if no contrary provisions are outlined in the primary document or other rules or sources relevant to resolving the specific question. In such cases, jurors should adhere to these standard guidelines for resolution.\n### Refuse to Arbitrate\n\nThe following questions must resolve as \"Refuse to Arbitrate\":\n\n**1. Invalid answers:** Questions in which none of the answers are valid.\n\n*Refuse to Arbitrate: A Prediction Market question: \"Which movie will win the Best Picture award at the 2024 Oscars Academy Awards?\" with outcomes \"Barbie\" and \"Poor Things\" (the actual winner was \"Oppenheimer\").*\n\n**2. Multiple outcomes:** Questions in which multiple outcomes are valid, unless the question allows multiple correct answers. In a multiple choice question in which only one correct answer is allowed, the fact that multiple outcomes could be valid at the same time does not make the question invalid if only one of those outcomes occurs.\n\n*Valid:​ A Prediction Market multiple choice question that allows more than one answer: \"What team will reach the semi-finals of Copa America 2021?\" with answers \"Brazil,\" \"Argentina,\" \"Uruguay,\" and \"Colombia\" (all of them except Uruguay reached the semi-finals).*\n\n*Refuse to Arbitrate: A Prediction Market multiple choice question in which only one correct answer is allowed: \"Who will be the Time person of the year 1937?\" with answers \"Chiang Kai-shek\" and \"Soong Mei-ling\" (they got the prize jointly).*\n\n**3. Prohibited questions:** Questions that directly incentivize immoral violent actions (such as murder, rape or unjust imprisonment) which could likely be performed by any participant.\n\n*Refuse to Arbitrate: A Prediction Market question: Will Donald Trump be alive on 01/12/2024? (Anyone could bet on \"No\" and kill him for a guaranteed profit. Anyone could bet on \"Yes\" to effectively put a bounty on his head).*\n\n*Refuse to Arbitrate: A Prediction Market question: Will Hera be a victim of swatting in 2024? (Anyone could falsely call the emergency services on him in order to win the bet)*\n\nThis must not prevent questions:\n\n* Whose topics are violent events not caused by human beings.\n\n*Valid:​ A Prediction Market question: How many people will die from COVID19 in 2024? (Viruses don't use prediction markets).*\n\n* Whose main source of uncertainty is not related to a potential violent action.\n\n*Valid:​ A Prediction Market question: Will Trump win the 2020 US presidential election? (The main source of uncertainty is the vote of US citizens, not a potential murder of a presidential candidate).*\n\n* Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.\n\n*Valid:​ A Prediction Market question: Will the US be engaged in a military conflict with a UN member state in 2024? (It's unlikely for the US to declare war in order to win a bet on this market).*\n\n*Valid:​ Will Derek Chauvin go to jail for the murder of George Flyod? (It's unlikely that the jurors would collude to make a wrong verdict in order to win this market).*\n\n### Default assumptions\n\nUnless stated otherwise, the following assumptions must be made:\n\n**4. Entities:** Entities are assumed to reference the most obvious entity with that name, taking the context of the question into account.\n\n*Example: A Prediction Market question: \"Will Michael Jordan receive the 2021 Turing award?\" refers to the computer scientist Michael I. Jordan whereas \"How many points will Michael Jordan score in the FIBA Americas Championship?\" refers to Michael J. Jordan, the basketball player.*\n\n**5. Units:** In case units are omitted, they are assumed to be the units which are the most often used in this particular situation.\n\n*Example: A Prediction Market question: \"Will a NFT be sold for more than one million in 2021?\" will be interpreted as \"Will a NFT be sold for more than 1,000,000 USD in 2021?\".*\n\n**6. Rounding rule:** If no specific rounding method is given, values are to be rounded to the nearest proposed value, unit or range. Unless otherwise stated, roundings are done middle toward 0. If no proposed rule, value, or unit is provided, the value shall default to the most commonly used standard in the specific context.\n\n*Example: In a Prediction Market question with outcomes -100, 0 and 100. 77->100, 50->0, -50 -> 0.*\n\n*Example: In a Prediction Market question with outcomes A: 0-2, B: 3-5 and C: 6+. 1->A, 8->C, 5.5->B.*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election?\". If Biden received 51.305859559% of the vote, the correct answer is 51% (rounding to the nearest whole percent).*\n\n*Example: In the Prediction Market question \"What percentage of the popular vote will Joe Biden receive in the 2020 United States Presidential Election? (2 decimals)\". If Biden received 51.305859559% of the vote, the correct answer is 51.31%.*\n\n### Resolving unclear questions\n\nIn general, if the question does not break a rule of the Refuse to Arbitrate section, reasonable efforts should be made to determine its outcome even if the question is not 100% technically perfect, and the following rules must be applied:\n\n**7. Objective interpretation:** Questions must be interpreted according to their context, as any average reasonable person would.\n\n*Example: \"Will there be more than ten thousand deaths caused by Coronavirus in the United States in 2024?\" should be interpreted as referring to COVID-19, and not other types of Coronavirus.*\n\n**8. Sources of truth:** If the question doesn't mention a specific source, the most credible outcome must be reported. In order to determine the credibility of an outcome, the quantity of sources and their credibility are to be taken into account. Credibility of sources and of outcomes must be assessed according to facts, not unproven beliefs.\n\n*Example: \"Will extraterrestrial lifeforms visit planet earth?\" will resolve to No, unless a number of credible sources announce it, despite some people reporting having experienced such encounters.*\n\n*Example: \"How many people will die of COVID-19 in 2024?\" should be answered according to numbers reported by renowned health organisations and not according to some public figures claiming COVID-19 to be a hoax.*\n\n**9. Equal interpretations:** If a question can have different interpretations, but all those interpretations lead to the same outcome, this outcome must be reported. If no interpretation is clearly more reasonable than the others, jurors must vote Refuse to Arbitrate.\n\n*Example: A Prediction Market question: \"Which party will win the October 2012 Czeck elections?\" Should be reported as \"Czech Social Democratic Party\". Even if there were both senatorial and regional elections at the same date and the election the question refers to is ambiguous, the \"Czech Social Democratic Party\" won both of them.*\n\n*Example: In a Prediction Market question: \"Which party will win the October 2015 Czech elections?\" jurors should vote Refuse to Arbitrate because \"Christian and Democratic Union – Czechoslovak People's Party\" won the senatorial election but \"ANO 2011\" won the regional ones.*\n\n**10. Precision in numerical values:** When the answer to a question is a numerical value and the exact value is uncertain, the first reported value that is reasonable based on common approximations must be accepted.\n\n*Example: If in a Prediction Market question, \"What will be the global potato production in tons for the year 2024?\", the first answer is 374,000,000, this answer should be accepted if the estimates provided range between 374 million and 375 million tons.*", - "requiredSkills": "Jurors in the Oracle Court should possess:\n- **Analytical Skills**: Ability to objectively assess a wide range of real-world event data, statistics, and sources, with precision and critical thinking.\n- **Understanding of Prediction Markets**: Familiarity with how prediction markets function." -} diff --git a/contracts/package.json b/contracts/package.json index 0cea3cc17..89adff2e7 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -55,6 +55,7 @@ "populate:courts:devnet": "hardhat populate:courts --from v2_devnet --network arbitrumSepoliaDevnet", "populate:courts:testnet": "hardhat populate:courts --from v2_testnet --network arbitrumSepolia", "populate:courts:mainnetNeo": "hardhat populate:courts --core-type neo --from v2_mainnet_neo --network arbitrum", + "populate:policiesUris": "scripts/setPoliciesURIs.sh config/policies.v2.{devnet,testnet,mainnet-neo}.json", "populate:policies:devnet": "hardhat populate:policy-registry --from v2_devnet --network arbitrumSepoliaDevnet", "populate:policies:testnet": "hardhat populate:policy-registry --from v2_testnet --network arbitrumSepolia", "populate:policies:mainnetNeo": "hardhat populate:policy-registry --core-type neo --from v2_mainnet_neo --network arbitrum", diff --git a/contracts/scripts/setPoliciesURIs.sh b/contracts/scripts/setPoliciesURIs.sh new file mode 100755 index 000000000..1bdc4e599 --- /dev/null +++ b/contracts/scripts/setPoliciesURIs.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Check if at least one input file is provided +if [ "$#" -lt 1 ]; then + echo "Usage: $0 [input_policies_file2 ...]" + exit 1 +fi + +# Process each input file +for INPUT_FILE in "$@"; do + # Validate file extension + if [[ ! "$INPUT_FILE" =~ \.json$ ]]; then + echo "Error: Input file $INPUT_FILE must have a .json extension" + continue + fi + + echo "Processing $INPUT_FILE..." + + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + INPUT_FILE_WITHOUT_EXTENSION="${INPUT_FILE%.json}" + POLICIES_DIR="$SCRIPT_DIR/../$INPUT_FILE_WITHOUT_EXTENSION" + HASHES_FILE=$(mktemp) + + echo "Creating $POLICIES_DIR directory..." + mkdir -p $POLICIES_DIR + + # Step 1: Create individual policy files and collect their hashes + echo "Creating individual policy files..." + echo "{" > "$HASHES_FILE" + first=true + + jq -c '.[]' "$INPUT_FILE" | while read -r policy; do + name=$(echo "$policy" | jq -r '.name' | tr ' ' '-') + court=$(echo "$policy" | jq -r '.court') + policy_filepath="$POLICIES_DIR/${name}-Policy.json" + + # Remove the uri field if it exists and save to a temporary file + echo "$policy" | jq 'del(.uri)' > "$policy_filepath" + + # Get IPFS hash + ipfs_hash=$(ipfs add -Q "$policy_filepath") + if [ -n "$ipfs_hash" ]; then + echo "Preparing $name Court ($court): ${name}-Policy.json" + # Add comma for all but the first entry + if [ "$first" = true ]; then + first=false + else + echo "," >> "$HASHES_FILE" + fi + # Store the hash with court as key + echo "\"$court\": \"$ipfs_hash\"" >> "$HASHES_FILE" + else + echo "Failed to get IPFS hash for ${name}-Policy.json" + rm "$HASHES_FILE" + continue 2 + fi + done + + echo "}" >> "$HASHES_FILE" + + # Step 2: Update the input file with URIs + echo "Updating URIs in $INPUT_FILE..." + jq --slurpfile hashes "$HASHES_FILE" ' + map(. + {uri: ("/ipfs/" + ($hashes[0][.court | tostring]))}) + ' "$INPUT_FILE" > "${INPUT_FILE}.tmp" && mv "${INPUT_FILE}.tmp" "$INPUT_FILE" + + rm "$HASHES_FILE" + echo "Done! URIs updated in $INPUT_FILE" + echo "----------------------------------------" +done \ No newline at end of file diff --git a/web/src/pages/Courts/CourtDetails/Description.tsx b/web/src/pages/Courts/CourtDetails/Description.tsx index 87159f784..f9bb221c6 100644 --- a/web/src/pages/Courts/CourtDetails/Description.tsx +++ b/web/src/pages/Courts/CourtDetails/Description.tsx @@ -62,9 +62,9 @@ const StyledTabs = styled(Tabs)` `; interface IPolicy { - description?: string; + purpose?: string; requiredSkills?: string; - summary?: string; + rules?: string; } const TABS = [ @@ -72,7 +72,7 @@ const TABS = [ text: "Purpose", value: 0, path: "purpose", - isVisible: (policy: IPolicy) => !!policy?.description, + isVisible: (policy: IPolicy) => !!policy?.purpose, }, { text: "Skills", @@ -84,7 +84,7 @@ const TABS = [ text: "Policy", value: 2, path: "policy", - isVisible: (policy: IPolicy) => !!policy?.summary, + isVisible: (policy: IPolicy) => !!policy?.rules, }, ]; @@ -115,9 +115,9 @@ const Description: React.FC = () => { - + - + 0 ? filteredTabs[0].path : ""} replace />} /> From 5af7b1b9ddfbfb176697f4a8464df5ac353c1237 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 24 Jan 2025 21:31:40 +0000 Subject: [PATCH 31/42] chore: reduced the dispute periods for devnet --- contracts/config/courts.v2.devnet.json | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contracts/config/courts.v2.devnet.json b/contracts/config/courts.v2.devnet.json index 23e9011c9..806e8451d 100644 --- a/contracts/config/courts.v2.devnet.json +++ b/contracts/config/courts.v2.devnet.json @@ -9,10 +9,10 @@ "feeForJuror": "100000000000", "jurorsForCourtJump": "511", "timesPerPeriod": [ - 280800, - 583200, - 583200, - 388800 + 120, + 240, + 240, + 600 ] }, { @@ -25,10 +25,10 @@ "feeForJuror": "100000000000", "jurorsForCourtJump": "31", "timesPerPeriod": [ - 140400, - 291600, - 291600, - 194400 + 120, + 240, + 240, + 600 ] }, { @@ -41,10 +41,10 @@ "feeForJuror": "100000000000", "jurorsForCourtJump": "63", "timesPerPeriod": [ - 280800, - 437400, - 437400, - 291600 + 120, + 240, + 240, + 600 ] }, { @@ -57,10 +57,10 @@ "feeForJuror": "100000000000", "jurorsForCourtJump": "63", "timesPerPeriod": [ - 140400, - 291600, - 291600, - 194400 + 120, + 240, + 240, + 600 ] }, { @@ -73,10 +73,10 @@ "feeForJuror": "100000000000", "jurorsForCourtJump": "31", "timesPerPeriod": [ - 140400, - 291600, - 291600, - 194400 + 120, + 240, + 240, + 600 ] } ] From 47e2cd6498a85363d88d165ee9acfd76640eabe2 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 24 Jan 2025 21:45:01 +0000 Subject: [PATCH 32/42] fix: xdai courts name --- contracts/config/policies.v2.mainnet-neo.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/config/policies.v2.mainnet-neo.json b/contracts/config/policies.v2.mainnet-neo.json index 6e65fe0e9..9f569b3ac 100644 --- a/contracts/config/policies.v2.mainnet-neo.json +++ b/contracts/config/policies.v2.mainnet-neo.json @@ -189,28 +189,28 @@ "uri": "/ipfs/QmXAVumYfMmezMQSbhYn33iCFxwqLguRztz7HcJaLnX1Z4" }, { - "name": "xDai Development Court", + "name": "Development Court", "purpose": "In this court, jurors will solve disputes involving the respect of specifications given by the client.", "rules": "### Example\nDeveloper does not respect indentation, does not name variables explicitly or has not made a clear file structure. In such cases, jurors should refuse the proposal made by the developer.", "requiredSkills": "This court requires a good level of familiarity with programming. Jurors who are not intermediate developers are advised to stake into this court only if they have some basics of low-level programming languages, ​​algorithmic and knowledge of good practices of development.", "court": 25, - "uri": "/ipfs/QmZ8gkBgHsomDpdJG6duJP2VN7u177NT7nsx2bjaLzJknN" + "uri": "/ipfs/QmfH2k1PmX4YufdZoAKwoGtdbjNZaxaTPdXB2uAs3rQsjh" }, { - "name": "xDai Solidity Court", + "name": "Solidity Court", "purpose": "", "rules": "If the disputed code is of significant size (> 500 code lines), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.", "requiredSkills": "This court requires a good level of solidity. Jurors who are not solidity intermediate developers are advised to stake into this court only if they also know how to make relatively simple contracts, know the main solidity hacks and can compute the complexity of simple functions.", "court": 26, - "uri": "/ipfs/QmNfeN8JW7GyawEQvsAuRAhm6aw5RpgJzYDKTPQGyPqvKA" + "uri": "/ipfs/QmPRckaaNLj9ycZH6otChTwbkDsBnhkNrXnarF5vD6rXKy" }, { - "name": "xDai Javascript Court", + "name": "Javascript Court", "purpose": "", "rules": "If the disputed code is of significant size (> 700 code lines), parties in the dispute should point out specific parts of the content which are being disputed. Otherwise, jurors should refuse to arbitrate.", "requiredSkills": "This court requires a good level of javascript. Jurors who are not javascript intermediate developers are advised to stake into this court only if they know the main frameworks/libraries (ExpressJs, React, EthersJs…) and be comfortable with testing, APIs or languages to interact with databases.", "court": 27, - "uri": "/ipfs/QmYdsdvqbbHKus5Y2djAkvTrtK8piPSmeM3WfPB9ntKk4K" + "uri": "/ipfs/QmS9JzVezbAioSXXcuQsMw31pNjg5jeaV8vtbpwY5cMG8b" }, { "name": "Corte de Curación en Español", From e8c013c1b6071d8602cf99ad96960678d13ca1f2 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Mon, 27 Jan 2025 10:55:07 +0100 Subject: [PATCH 33/42] refactor: header styling and reactmarkdown --- .../pages/Courts/CourtDetails/Description.tsx | 18 ++++++++---------- web/src/styles/global-style.ts | 10 ++++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/Description.tsx b/web/src/pages/Courts/CourtDetails/Description.tsx index f9bb221c6..e65de7ce9 100644 --- a/web/src/pages/Courts/CourtDetails/Description.tsx +++ b/web/src/pages/Courts/CourtDetails/Description.tsx @@ -17,32 +17,30 @@ const Container = styled.div` const TextContainer = styled.div` width: 100%; padding: 12px 0; +`; +const StyledReactMarkdown = styled(ReactMarkdown)` p { word-break: break-word; } - li { - line-height: 1.5em; - margin-top: 0.5em; + ul, + ol { + li + li { + margin-top: 8px; + } } h1 { margin: 16px 0 16px 0; - font-size: 24px; - line-height: 32px; } h2 { margin: 16px 0 16px 0; - font-size: 20px; - line-height: 24px; } h3 { margin: 16px 0 16px 0; - font-size: 18px; - line-height: 20px; } a { @@ -128,6 +126,6 @@ const Description: React.FC = () => { }; const formatMarkdown = (markdown?: string) => - markdown ? {markdown.replace(/\n/g, " \n")} : ; + markdown ? {markdown.replace(/\n/g, " \n")} : ; export default Description; diff --git a/web/src/styles/global-style.ts b/web/src/styles/global-style.ts index fe8cba478..94edd40d4 100644 --- a/web/src/styles/global-style.ts +++ b/web/src/styles/global-style.ts @@ -46,7 +46,7 @@ export const GlobalStyle = createGlobalStyle` h2 { margin: 0 0 16px 0; - font-weight: 600; + font-weight: 400; font-size: 24px; line-height: 32px; color: ${({ theme }) => theme.primaryText}; @@ -93,7 +93,7 @@ export const GlobalStyle = createGlobalStyle` color: ${({ theme }) => theme.primaryBlue}; transition: color 0.1s; } - + hr { opacity: 1; border: 1px solid ${({ theme }) => theme.stroke}; @@ -102,12 +102,14 @@ export const GlobalStyle = createGlobalStyle` svg, img { display: inline-block; vertical-align: middle; - visibility: visible; - + visibility: visible; } ul, ol { li { + font-weight: 400; + font-size: 16px; + line-height: 24px; color: ${({ theme }) => theme.primaryText}; } } From b5a85e6634eb570c4106101ee3c9b2f8e1f2d4f4 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Mon, 27 Jan 2025 12:05:42 +0100 Subject: [PATCH 34/42] fix: use more adequate sizing for inner component --- web/src/pages/Courts/CourtDetails/Description.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/src/pages/Courts/CourtDetails/Description.tsx b/web/src/pages/Courts/CourtDetails/Description.tsx index e65de7ce9..6cae460bc 100644 --- a/web/src/pages/Courts/CourtDetails/Description.tsx +++ b/web/src/pages/Courts/CourtDetails/Description.tsx @@ -33,14 +33,20 @@ const StyledReactMarkdown = styled(ReactMarkdown)` h1 { margin: 16px 0 16px 0; + font-size: 20px; + line-height: 26px; } h2 { margin: 16px 0 16px 0; + font-size: 20px; + line-height: 26px; } h3 { margin: 16px 0 16px 0; + font-size: 18px; + line-height: 24px; } a { From a623f8958d59e2ee9173be37ca48755551825b03 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 27 Jan 2025 16:47:50 +0530 Subject: [PATCH 35/42] refactor(web): graphql-batching-and-optimisations --- web/package.json | 2 + web/src/consts/index.ts | 3 +- web/src/context/GraphqlBatcher.tsx | 45 +++++++++++++------ web/src/hooks/queries/useAllCasesQuery.ts | 2 + .../hooks/queries/useClassicAppealQuery.ts | 3 +- web/src/hooks/queries/useCourtDetails.ts | 3 +- web/src/hooks/queries/useCourtTree.ts | 2 + .../hooks/queries/useDisputeDetailsQuery.ts | 3 +- .../queries/useDisputeMaintenanceQuery.ts | 2 + .../queries/useJurorStakeDetailsQuery.ts | 3 +- web/src/hooks/queries/useUser.ts | 2 + web/src/hooks/queries/useVotingHistory.ts | 3 +- 12 files changed, 54 insertions(+), 19 deletions(-) diff --git a/web/package.json b/web/package.json index efe863eb5..2224cf711 100644 --- a/web/package.json +++ b/web/package.json @@ -78,6 +78,8 @@ }, "dependencies": { "@cyntler/react-doc-viewer": "^1.17.0", + "@graphql-tools/batch-execute": "^9.0.11", + "@graphql-tools/utils": "^10.7.2", "@kleros/kleros-app": "workspace:^", "@kleros/kleros-sdk": "workspace:^", "@kleros/kleros-v2-contracts": "workspace:^", diff --git a/web/src/consts/index.ts b/web/src/consts/index.ts index 9592cedac..219ff0e9c 100644 --- a/web/src/consts/index.ts +++ b/web/src/consts/index.ts @@ -7,6 +7,7 @@ export { ArbitratorTypes }; export const ONE_BASIS_POINT = 10000n; export const REFETCH_INTERVAL = 5000; +export const STALE_TIME = 1000; export const IPFS_GATEWAY = import.meta.env.REACT_APP_IPFS_GATEWAY || "https://cdn.kleros.link"; export const HERMES_TELEGRAM_BOT_URL = @@ -20,7 +21,7 @@ export const GIT_URL = `https://github.com/kleros/kleros-v2/tree/${gitCommitHash export const RELEASE_VERSION = version; // https://www.w3.org/TR/2012/WD-html-markup-20120329/input.email.html#input.email.attrs.value.single -// eslint-disable-next-line security/detect-unsafe-regex + export const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export const TELEGRAM_REGEX = /^@\w{5,32}$/; diff --git a/web/src/context/GraphqlBatcher.tsx b/web/src/context/GraphqlBatcher.tsx index bd7b1572a..5e522567c 100644 --- a/web/src/context/GraphqlBatcher.tsx +++ b/web/src/context/GraphqlBatcher.tsx @@ -1,12 +1,13 @@ import React, { useMemo, createContext, useContext } from "react"; +import { createBatchingExecutor } from "@graphql-tools/batch-execute"; +import { AsyncExecutor, ExecutionResult } from "@graphql-tools/utils"; import { TypedDocumentNode } from "@graphql-typed-document-node/core"; import { create, windowedFiniteBatchScheduler, Batcher } from "@yornaath/batshit"; import { request } from "graphql-request"; import { debounceErrorToast } from "utils/debounceErrorToast"; import { getGraphqlUrl } from "utils/getGraphqlUrl"; - interface IGraphqlBatcher { graphqlBatcher: Batcher; } @@ -21,19 +22,37 @@ interface IQuery { const Context = createContext(undefined); +const executor: AsyncExecutor = async ({ document, variables, extensions }) => { + console.log({ url: extensions.url }); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const result = request(extensions.url, document, variables).then((res) => ({ + data: res, + })) as Promise; + + return result; + } catch (error) { + console.error("Graph error: ", { error }); + debounceErrorToast("Graph query error: failed to fetch data."); + return { data: {} }; + } +}; + +const myBatchExec = createBatchingExecutor(executor); + const fetcher = async (queries: IQuery[]) => { - const promises = queries.map(async ({ id, document, variables, isDisputeTemplate, chainId }) => { - const url = getGraphqlUrl(isDisputeTemplate ?? false, chainId); - try { - return request(url, document, variables).then((result) => ({ id, result })); - } catch (error) { - console.error("Graph error: ", { error }); - debounceErrorToast("Graph query error: failed to fetch data."); - return { id, result: {} }; - } - }); - const data = await Promise.all(promises); - return data; + const batchdata = await Promise.all( + queries.map(({ document, variables, isDisputeTemplate, chainId }) => + myBatchExec({ document, variables, extensions: { url: getGraphqlUrl(isDisputeTemplate ?? false, chainId) } }) + ) + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const processedData = batchdata.map((data, index) => ({ id: queries[index].id, result: data.data })); + return processedData; }; const GraphqlBatcherProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { diff --git a/web/src/hooks/queries/useAllCasesQuery.ts b/web/src/hooks/queries/useAllCasesQuery.ts index 49cc787c8..e24bb2102 100644 --- a/web/src/hooks/queries/useAllCasesQuery.ts +++ b/web/src/hooks/queries/useAllCasesQuery.ts @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { STALE_TIME } from "src/consts"; import { graphql } from "src/graphql"; import { AllCasesQuery } from "src/graphql/graphql"; @@ -20,6 +21,7 @@ export const useAllCasesQuery = () => { const { graphqlBatcher } = useGraphqlBatcher(); return useQuery({ queryKey: [`allCasesQuery`], + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: allCasesQuery, variables: {} }), }); diff --git a/web/src/hooks/queries/useClassicAppealQuery.ts b/web/src/hooks/queries/useClassicAppealQuery.ts index d12a76238..8a5ddf073 100644 --- a/web/src/hooks/queries/useClassicAppealQuery.ts +++ b/web/src/hooks/queries/useClassicAppealQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { REFETCH_INTERVAL } from "consts/index"; +import { REFETCH_INTERVAL, STALE_TIME } from "consts/index"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; import { graphql } from "src/graphql"; @@ -44,6 +44,7 @@ export const useClassicAppealQuery = (id?: string | number) => { queryKey: [`classicAppealQuery${id}`], enabled: isEnabled, refetchInterval: REFETCH_INTERVAL, + staleTime: STALE_TIME, queryFn: async () => isEnabled ? await graphqlBatcher.fetch({ diff --git a/web/src/hooks/queries/useCourtDetails.ts b/web/src/hooks/queries/useCourtDetails.ts index 296d4aa09..321c6ce33 100644 --- a/web/src/hooks/queries/useCourtDetails.ts +++ b/web/src/hooks/queries/useCourtDetails.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { REFETCH_INTERVAL } from "consts/index"; +import { REFETCH_INTERVAL, STALE_TIME } from "consts/index"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; import { graphql } from "src/graphql"; @@ -36,6 +36,7 @@ export const useCourtDetails = (id?: string) => { queryKey: [`courtDetails${id}`], enabled: isEnabled, refetchInterval: REFETCH_INTERVAL, + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: courtDetailsQuery, variables: { id } }), }); diff --git a/web/src/hooks/queries/useCourtTree.ts b/web/src/hooks/queries/useCourtTree.ts index dffab3e2a..5eb70814d 100644 --- a/web/src/hooks/queries/useCourtTree.ts +++ b/web/src/hooks/queries/useCourtTree.ts @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { STALE_TIME } from "src/consts"; import { graphql } from "src/graphql"; import { CourtTreeQuery } from "src/graphql/graphql"; export type { CourtTreeQuery }; @@ -39,6 +40,7 @@ export const useCourtTree = () => { const { graphqlBatcher } = useGraphqlBatcher(); return useQuery({ queryKey: ["courtTreeQuery"], + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: courtTreeQuery, variables: {} }), }); diff --git a/web/src/hooks/queries/useDisputeDetailsQuery.ts b/web/src/hooks/queries/useDisputeDetailsQuery.ts index 71a417904..2aed81c0f 100644 --- a/web/src/hooks/queries/useDisputeDetailsQuery.ts +++ b/web/src/hooks/queries/useDisputeDetailsQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { REFETCH_INTERVAL } from "consts/index"; +import { REFETCH_INTERVAL, STALE_TIME } from "consts/index"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; import { graphql } from "src/graphql"; @@ -48,6 +48,7 @@ export const useDisputeDetailsQuery = (id?: string | number) => { queryKey: [`disputeDetailsQuery${id}`], enabled: isEnabled, refetchInterval: REFETCH_INTERVAL, + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), diff --git a/web/src/hooks/queries/useDisputeMaintenanceQuery.ts b/web/src/hooks/queries/useDisputeMaintenanceQuery.ts index 0703804d2..191684c2f 100644 --- a/web/src/hooks/queries/useDisputeMaintenanceQuery.ts +++ b/web/src/hooks/queries/useDisputeMaintenanceQuery.ts @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { STALE_TIME } from "src/consts"; import { graphql } from "src/graphql"; import { DisputeMaintenanceQuery } from "src/graphql/graphql"; import { isUndefined } from "src/utils"; @@ -40,6 +41,7 @@ const useDisputeMaintenanceQuery = (id?: string) => { return useQuery({ queryKey: [`disputeMaintenanceQuery-${id}`], enabled: isEnabled, + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), diff --git a/web/src/hooks/queries/useJurorStakeDetailsQuery.ts b/web/src/hooks/queries/useJurorStakeDetailsQuery.ts index 51b54c51a..21fdc3d69 100644 --- a/web/src/hooks/queries/useJurorStakeDetailsQuery.ts +++ b/web/src/hooks/queries/useJurorStakeDetailsQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { REFETCH_INTERVAL } from "consts/index"; +import { REFETCH_INTERVAL, STALE_TIME } from "consts/index"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; import { graphql } from "src/graphql"; @@ -29,6 +29,7 @@ export const useJurorStakeDetailsQuery = (userId?: string) => { queryKey: [`jurorStakeDetails${userId}`], enabled: isEnabled, refetchInterval: REFETCH_INTERVAL, + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: jurorStakeDetailsQuery, variables: { userId } }), }); diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index d453035c9..5da4bc1e0 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -3,6 +3,7 @@ import { Address } from "viem"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { STALE_TIME } from "src/consts"; import { graphql } from "src/graphql"; import { UserQuery, Dispute_Filter, UserDisputeFilterQuery, UserDetailsFragment } from "src/graphql/graphql"; export type { UserQuery, UserDetailsFragment }; @@ -58,6 +59,7 @@ export const useUserQuery = (address?: Address, where?: Dispute_Filter) => { return useQuery({ queryKey: [`userQuery${address?.toLowerCase()}`], enabled: isEnabled, + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), diff --git a/web/src/hooks/queries/useVotingHistory.ts b/web/src/hooks/queries/useVotingHistory.ts index e1210ddaa..cdebd9186 100644 --- a/web/src/hooks/queries/useVotingHistory.ts +++ b/web/src/hooks/queries/useVotingHistory.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { REFETCH_INTERVAL } from "consts/index"; +import { REFETCH_INTERVAL, STALE_TIME } from "consts/index"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; import { graphql } from "src/graphql"; @@ -59,6 +59,7 @@ export const useVotingHistory = (disputeID?: string) => { queryKey: [`VotingHistory${disputeID}`], enabled: isEnabled, refetchInterval: REFETCH_INTERVAL, + staleTime: STALE_TIME, queryFn: async () => await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: votingHistoryQuery, variables: { disputeID } }), }); From 9a998247f761b006f9ef38e9fcaa8b6818f480e6 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 27 Jan 2025 16:48:14 +0530 Subject: [PATCH 36/42] chore: yarn-lockfile --- yarn.lock | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 363126cb4..0b6abee22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4510,6 +4510,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/batch-execute@npm:^9.0.11": + version: 9.0.11 + resolution: "@graphql-tools/batch-execute@npm:9.0.11" + dependencies: + "@graphql-tools/utils": "npm:^10.7.0" + dataloader: "npm:^2.2.3" + tslib: "npm:^2.8.1" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/6180424a5fa36a446baa665a92cff0332a566b1bd7481e2641c9d0aa2a7a47a24d21a9b90bb3d7f4c0d5a7331fc9e623fe43746f07e5eb0654419a29d860a940 + languageName: node + linkType: hard + "@graphql-tools/code-file-loader@npm:^8.0.0": version: 8.0.1 resolution: "@graphql-tools/code-file-loader@npm:8.0.1" @@ -4837,6 +4850,20 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/utils@npm:^10.7.0, @graphql-tools/utils@npm:^10.7.2": + version: 10.7.2 + resolution: "@graphql-tools/utils@npm:10.7.2" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + cross-inspect: "npm:1.0.1" + dset: "npm:^3.1.4" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/b4725b081e5ff5c1441036db76ce907a6fe9b4c94aa9ceb070f75541b2297c3cccaa182f91d214f9abe6d89df33d8df51e055afbc4e382b01e8d8fb7c2f6edf6 + languageName: node + linkType: hard + "@graphql-tools/wrap@npm:^10.0.0": version: 10.0.0 resolution: "@graphql-tools/wrap@npm:10.0.0" @@ -5609,6 +5636,8 @@ __metadata: "@eslint/js": "npm:^9.15.0" "@graphql-codegen/cli": "npm:^5.0.3" "@graphql-codegen/client-preset": "npm:^4.5.1" + "@graphql-tools/batch-execute": "npm:^9.0.11" + "@graphql-tools/utils": "npm:^10.7.2" "@kleros/kleros-app": "workspace:^" "@kleros/kleros-sdk": "workspace:^" "@kleros/kleros-v2-contracts": "workspace:^" @@ -15796,6 +15825,15 @@ __metadata: languageName: node linkType: hard +"cross-inspect@npm:1.0.1": + version: 1.0.1 + resolution: "cross-inspect@npm:1.0.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/7c1e02e0a9670b62416a3ea1df7ae880fdad3aa0a857de8932c4e5f8acd71298c7e3db9da8e9da603f5692cd1879938f5e72e34a9f5d1345987bef656d117fc1 + languageName: node + linkType: hard + "cross-spawn@npm:7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -16314,6 +16352,13 @@ __metadata: languageName: node linkType: hard +"dataloader@npm:^2.2.3": + version: 2.2.3 + resolution: "dataloader@npm:2.2.3" + checksum: 10/83fe6259abe00ae64c5f48252ef59d8e5fcabda9fd4d26685f14a76eeca596bf6f9500d9f22a0094c50c3ea782a0977728f9367e232dfa0fdb5c9d646de279b2 + languageName: node + linkType: hard + "date-fns@npm:^1.27.2": version: 1.30.1 resolution: "date-fns@npm:1.30.1" @@ -17170,6 +17215,13 @@ __metadata: languageName: node linkType: hard +"dset@npm:^3.1.4": + version: 3.1.4 + resolution: "dset@npm:3.1.4" + checksum: 10/6268c9e2049c8effe6e5a1952f02826e8e32468b5ced781f15f8f3b1c290da37626246fec014fbdd1503413f981dff6abd8a4c718ec9952fd45fccb6ac9de43f + languageName: node + linkType: hard + "duplexer3@npm:^0.1.4": version: 0.1.5 resolution: "duplexer3@npm:0.1.5" @@ -34460,7 +34512,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3": +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 From 8bb3ad2eac16e1d09a43865b2b88671accf40514 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 27 Jan 2025 17:12:11 +0530 Subject: [PATCH 37/42] chore(web): remove-log --- web/src/context/GraphqlBatcher.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/context/GraphqlBatcher.tsx b/web/src/context/GraphqlBatcher.tsx index 5e522567c..8ea4810db 100644 --- a/web/src/context/GraphqlBatcher.tsx +++ b/web/src/context/GraphqlBatcher.tsx @@ -23,8 +23,6 @@ interface IQuery { const Context = createContext(undefined); const executor: AsyncExecutor = async ({ document, variables, extensions }) => { - console.log({ url: extensions.url }); - try { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore From 1fb3749fbff9a194520711b7eb57be25e423124e Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 27 Jan 2025 18:00:55 +0530 Subject: [PATCH 38/42] refactor(web): function-name-update --- web/src/context/GraphqlBatcher.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/context/GraphqlBatcher.tsx b/web/src/context/GraphqlBatcher.tsx index 8ea4810db..23ffdb1b4 100644 --- a/web/src/context/GraphqlBatcher.tsx +++ b/web/src/context/GraphqlBatcher.tsx @@ -38,12 +38,12 @@ const executor: AsyncExecutor = async ({ document, variables, extensions }) => { } }; -const myBatchExec = createBatchingExecutor(executor); +const batchExec = createBatchingExecutor(executor); const fetcher = async (queries: IQuery[]) => { const batchdata = await Promise.all( queries.map(({ document, variables, isDisputeTemplate, chainId }) => - myBatchExec({ document, variables, extensions: { url: getGraphqlUrl(isDisputeTemplate ?? false, chainId) } }) + batchExec({ document, variables, extensions: { url: getGraphqlUrl(isDisputeTemplate ?? false, chainId) } }) ) ); From 2343dfaa3f903e8cd6c7c8f13e13cb46933920c0 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 27 Jan 2025 13:26:55 +0000 Subject: [PATCH 39/42] chore: codeclimate --- .codeclimate.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 72d7214aa..77f80363a 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,3 +1,10 @@ +checks: + similar_code: + exclude_paths: + - "contracts/tests/" + complexity: + exclude_paths: + - "contracts/tests/" engines: tslint: enabled: true @@ -7,3 +14,5 @@ engines: exclude_paths: - "web/src/graphql/generated.ts" - "contracts/deployments/" + - "contracts/config/" + - "kleros-sdk/config/" From e18c73c692721ad2874cc02d5584deba605e0c40 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 27 Jan 2025 13:50:09 +0000 Subject: [PATCH 40/42] chore: smaller well-done.svg --- .../dispute-resolver/well-done.svg | 102 +++++++++--------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg b/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg index 4e94ed36a..5bfeb7e76 100644 --- a/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg +++ b/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg @@ -1,78 +1,80 @@ - - - + + + - - - - - - - - - - + + + + + + + + + + + - - + + + + - - + + - - + + - - - + + + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - + + - + - - + + - - + + - - + + - + - + - + - - + From f191315c3ef65bd7f0e3d5857b3f6efa07e780ce Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 27 Jan 2025 14:02:53 +0000 Subject: [PATCH 41/42] fix: regex vulnerable to super-linear runtime due to excessive backtracking --- web/src/utils/commify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/utils/commify.ts b/web/src/utils/commify.ts index 47fdf73a3..533f5a776 100644 --- a/web/src/utils/commify.ts +++ b/web/src/utils/commify.ts @@ -1,7 +1,7 @@ export function commify(value: string | number): string { const comps = String(value).split("."); - if (!String(value).match(/^-?[0-9]*\.?[0-9]*$/)) { + if (!String(value).match(/^-?\d+(\.\d+)?$/)) { return "0"; } From 150cecdd280408ffd1a661f98070f2043966fb89 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:17:51 +0100 Subject: [PATCH 42/42] fix: miniguides svg --- .../dispute-resolver/well-done.svg | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg b/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg index 5bfeb7e76..e00059dde 100644 --- a/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg +++ b/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg @@ -1,16 +1,16 @@ - - + + - - - - - - - - + + + + + + + + @@ -20,8 +20,8 @@ - - + + @@ -31,25 +31,25 @@ - - - - + + + + - + - - + + - + - - + + - + - - + + @@ -77,4 +77,4 @@ - + \ No newline at end of file