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/" diff --git a/contracts/config/courts.v2.devnet.json b/contracts/config/courts.v2.devnet.json index c1fdd831c..806e8451d 100644 --- a/contracts/config/courts.v2.devnet.json +++ b/contracts/config/courts.v2.devnet.json @@ -1,47 +1,82 @@ [ { + "name": "General Court", "id": 1, "parent": 0, "hiddenVotes": true, - "minStake": "520000000000000000000", + "minStake": "1500000000000000000", "alpha": "5000", - "feeForJuror": "15000000000000000000", + "feeForJuror": "100000000000", "jurorsForCourtJump": "511", "timesPerPeriod": [ - 280800, - 583200, - 583200, - 388800 + 120, + 240, + 240, + 600 ] }, { + "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, - 291600, - 194400 + 120, + 240, + 240, + 600 ] }, { + "name": "English Language", "id": 3, "parent": 1, "hiddenVotes": false, - "minStake": "1200000000000000000000", + "minStake": "2000000000000000000", "alpha": "5000", - "feeForJuror": "22000000000000000000", + "feeForJuror": "100000000000", "jurorsForCourtJump": "63", "timesPerPeriod": [ - 280800, - 437400, - 437400, - 291600 + 120, + 240, + 240, + 600 + ] + }, + { + "name": "Corte de Disputas de Consumo y Vecindad", + "id": 4, + "parent": 1, + "hiddenVotes": false, + "minStake": "2000000000000000000", + "alpha": "5000", + "feeForJuror": "100000000000", + "jurorsForCourtJump": "63", + "timesPerPeriod": [ + 120, + 240, + 240, + 600 + ] + }, + { + "name": "Oracle Court", + "id": 5, + "parent": 1, + "hiddenVotes": false, + "minStake": "2000000000000000000", + "alpha": "5000", + "feeForJuror": "100000000000", + "jurorsForCourtJump": "31", + "timesPerPeriod": [ + 120, + 240, + 240, + 600 ] } ] 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..1fd7918e2 100644 --- a/contracts/config/policies.v2.devnet.json +++ b/contracts/config/policies.v2.devnet.json @@ -1,25 +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\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": 2, - "uri": "/ipfs/QmTGV621hG2JFsoAiHtd2Y7hWd7msgc3XdsGwqhjzntmKm/Curation-Court-Policy.json" + "uri": "/ipfs/QmPpey7rFbPi25Djcb4ppcGaxR4pojLgpGW2jhUvKxvE5q" }, { "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.", + "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", + "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/QmdfPa7UZxc7iVquQWiTmmRG3n6RSkwN16aXFeK7XyLvjr" + }, + { + "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/QmT8DAjUbzzEo2e9oPpJSDH2QzswfNeWAsxoDH3zsGrtkH" } ] diff --git a/contracts/config/policies.v2.mainnet-neo.json b/contracts/config/policies.v2.mainnet-neo.json index 6e12a02b7..9f569b3ac 100644 --- a/contracts/config/policies.v2.mainnet-neo.json +++ b/contracts/config/policies.v2.mainnet-neo.json @@ -1,231 +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.", + "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/QmbgUL2iv9XH3jui7xdLBXp2Hqe4VqGnNkK7PnAorJ8XQa/xDai-Development-Court-Policy.json" + "uri": "/ipfs/QmfH2k1PmX4YufdZoAKwoGtdbjNZaxaTPdXB2uAs3rQsjh" }, { - "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": "", + "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/QmQbyk1qnD4e4MQrwSr6a21w2t82YJEMxU3F7QTYKkxuNS/xDai-Solidity-Court-Policy.json" + "uri": "/ipfs/QmPRckaaNLj9ycZH6otChTwbkDsBnhkNrXnarF5vD6rXKy" }, { - "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": "", + "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/Qme15AUfpvLX3iwEtqswe26PQHMmKnF4eWGywBPqbkdqcD/xDai-Javascript-Court-Policy.json" + "uri": "/ipfs/QmS9JzVezbAioSXXcuQsMw31pNjg5jeaV8vtbpwY5cMG8b" }, { "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": "Blockchain No Técnica", - "description": "", - "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.", + "name": "Corte de Disputas de Consumo y Vecindad", + "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/QmVxh7KmFrimGz6WMvLMRXZhwQFDoj28KEYLTpYoUTGwAj/xDai-Blockchain-No-Tecnica-Policy.json" + "uri": "/ipfs/Qmczrn2DgdKGnacdvKRYwCk7JkeyTCokdqQycWdetYrxGC" + }, + { + "name": "Oracle Court", + "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/QmVFKNM1F3YnH2DVFh1Xd6epL9Asum2xBm9kGUQeXypAN5" } ] diff --git a/contracts/config/policies.v2.testnet.json b/contracts/config/policies.v2.testnet.json index db5a1939c..1fd7918e2 100644 --- a/contracts/config/policies.v2.testnet.json +++ b/contracts/config/policies.v2.testnet.json @@ -1,25 +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\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": 2, - "uri": "/ipfs/QmTGV621hG2JFsoAiHtd2Y7hWd7msgc3XdsGwqhjzntmKm/Curation-Court-Policy.json" + "uri": "/ipfs/QmPpey7rFbPi25Djcb4ppcGaxR4pojLgpGW2jhUvKxvE5q" }, { "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.", + "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", + "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/QmdfPa7UZxc7iVquQWiTmmRG3n6RSkwN16aXFeK7XyLvjr" + }, + { + "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/QmT8DAjUbzzEo2e9oPpJSDH2QzswfNeWAsxoDH3zsGrtkH" } ] 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/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..f6439c91f --- /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", { + contract: "DisputeKitClassic", + 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/package.json b/contracts/package.json index e2441d609..89adff2e7 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", @@ -51,6 +52,13 @@ "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: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", "release:patch": "scripts/publish.sh patch", "release:minor": "scripts/publish.sh minor", "release:major": "scripts/publish.sh major", @@ -69,6 +77,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 +89,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 +116,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/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( 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/contracts/src/arbitration/DisputeTemplateRegistry.sol b/contracts/src/arbitration/DisputeTemplateRegistry.sol index bf1b77c6b..ca22c6400 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 * // // ************************************* // @@ -31,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 e3bc02150..ceaa6b4e0 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -8,19 +8,19 @@ pragma solidity 0.8.24; -import "./KlerosCoreBase.sol"; -import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; -import {Initializable} from "../proxy/Initializable.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, Initializable { +contract KlerosCore is KlerosCoreBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } @@ -48,7 +48,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..85541c5fa 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -11,13 +11,15 @@ 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 {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 { +abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable { using SafeERC20 for IERC20; // ************************************* // @@ -193,7 +195,7 @@ abstract contract KlerosCoreBase is IArbitratorV2 { // * Constructor * // // ************************************* // - function _initialize( + function __KlerosCoreBase_initialize( address _governor, address _guardian, IERC20 _pinakion, @@ -204,7 +206,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..205a50720 100644 --- a/contracts/src/arbitration/KlerosCoreNeo.sol +++ b/contracts/src/arbitration/KlerosCoreNeo.sol @@ -8,15 +8,15 @@ pragma solidity 0.8.24; -import "./KlerosCoreBase.sol"; -import {UUPSProxiable} from "../proxy/UUPSProxiable.sol"; -import {Initializable} from "../proxy/Initializable.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, Initializable { +contract KlerosCoreNeo is KlerosCoreBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // @@ -28,7 +28,7 @@ contract KlerosCoreNeo is KlerosCoreBase, UUPSProxiable, Initializable { // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } @@ -58,7 +58,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/PolicyRegistry.sol b/contracts/src/arbitration/PolicyRegistry.sol index cec29e76c..7150ba0f1 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 * // // ************************************* // @@ -38,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 4db44c5c4..828f674c0 100644 --- a/contracts/src/arbitration/SortitionModule.sol +++ b/contracts/src/arbitration/SortitionModule.sol @@ -10,18 +10,18 @@ pragma solidity 0.8.24; -import "./SortitionModuleBase.sol"; -import "../proxy/UUPSProxiable.sol"; -import "../proxy/Initializable.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, Initializable { +contract SortitionModule is SortitionModuleBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } @@ -41,7 +41,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..2d6e3a9d8 100644 --- a/contracts/src/arbitration/SortitionModuleBase.sol +++ b/contracts/src/arbitration/SortitionModuleBase.sol @@ -10,15 +10,17 @@ pragma solidity 0.8.24; -import "./KlerosCore.sol"; -import "./interfaces/ISortitionModule.sol"; -import "./interfaces/IDisputeKit.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 { +abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSProxiable { // ************************************* // // * Enums / Structs * // // ************************************* // @@ -89,14 +91,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..5d725e277 100644 --- a/contracts/src/arbitration/SortitionModuleNeo.sol +++ b/contracts/src/arbitration/SortitionModuleNeo.sol @@ -10,13 +10,13 @@ pragma solidity 0.8.24; -import "./SortitionModuleBase.sol"; -import "../proxy/UUPSProxiable.sol"; -import "../proxy/Initializable.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, Initializable { +contract SortitionModuleNeo is SortitionModuleBase { + string public constant override version = "0.8.0"; + // ************************************* // // * Storage * // // ************************************* // @@ -29,7 +29,7 @@ contract SortitionModuleNeo is SortitionModuleBase, UUPSProxiable, Initializable // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } @@ -53,7 +53,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/devtools/KlerosCoreRuler.sol b/contracts/src/arbitration/devtools/KlerosCoreRuler.sol index 3c5aebf8b..2100ad1c3 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 * // // ************************************* // @@ -164,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 12d4e45c5..ef4591abd 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -8,10 +8,7 @@ pragma solidity 0.8.24; -import "../KlerosCore.sol"; -import "../interfaces/IDisputeKit.sol"; -import "../../proxy/UUPSProxiable.sol"; -import "../../proxy/Initializable.sol"; +import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; /// @title DisputeKitClassic /// Dispute kit implementation of the Kleros v1 features including: @@ -19,130 +16,14 @@ 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 { + string public constant override version = "0.8.0"; // ************************************* // // * Constructor * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } @@ -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..b6865d9f7 --- /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, 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: +/// - 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, 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. + } + + // ************************************* // + // * 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 new file mode 100644 index 000000000..2318f1dfe --- /dev/null +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT + +/// @custom:authors: [@unknownunknown1, @jaybuidl] +/// @custom:reviewers: [] +/// @custom:auditors: [] +/// @custom:bounties: [] +/// @custom:deployments: [] + +pragma solidity 0.8.24; + +import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; + +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` 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. +contract DisputeKitGated is DisputeKitClassicBase { + string public constant override version = "0.8.0"; + + // ************************************* // + // * Storage * // + // ************************************* // + + 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 * // + // ************************************* // + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @dev Initializer. + /// @param _governor The governor's address. + /// @param _core The KlerosCore arbitrator. + /// @param _tokenGate The token used for gating access. + /// @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; + } + + // ************************ // + // * 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 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 * // + // ************************************* // + + /// @inheritdoc DisputeKitClassicBase + function _postDrawCheck( + Round storage _round, + uint256 _coreDisputeID, + address _juror + ) internal view override returns (bool) { + if (!super._postDrawCheck(_round, _coreDisputeID, _juror)) return false; + + if (isERC1155) { + return IBalanceHolderERC1155(tokenGate).balanceOf(_juror, tokenId) > 0; + } else { + return IBalanceHolder(tokenGate).balanceOf(_juror) > 0; + } + } +} \ No newline at end of file diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index dc571cfbf..0c6d98d5e 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -8,10 +8,7 @@ pragma solidity 0.8.24; -import "../KlerosCore.sol"; -import "../interfaces/IDisputeKit.sol"; -import "../../proxy/UUPSProxiable.sol"; -import "../../proxy/Initializable.sol"; +import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol"; interface IProofOfHumanity { /// @dev Return true if the submission is registered and not expired. @@ -26,132 +23,20 @@ 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 { + string public constant override version = "0.8.0"; // ************************************* // // * 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 * // // ************************************* // - /// @dev Constructor, initializing the implementation to reduce attack surface. + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } @@ -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,16 @@ 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); + /// @inheritdoc DisputeKitClassicBase + 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/arbitration/evidence/EvidenceModule.sol b/contracts/src/arbitration/evidence/EvidenceModule.sol index 3f7e80c29..21405bc6d 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 * // // ************************************* // @@ -35,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 d3e5be37c..1f2e888e2 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 * // // ************************************* // @@ -180,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 a2d3f2c63..7c79651d8 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 * // // ************************************* // @@ -66,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 d69889edf..81649b072 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 * // // ************************************* // @@ -71,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 180b0e878..ade0de2e6 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 * // // ************************************* // @@ -57,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/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) {} } diff --git a/contracts/src/proxy/UUPSProxiable.sol b/contracts/src/proxy/UUPSProxiable.sol index 258a7a622..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,6 +124,10 @@ abstract contract UUPSProxiable { return IMPLEMENTATION_SLOT; } + /// @dev Returns the version of the implementation. + /// @return Version string. + function version() external view virtual returns (string memory); + // ************************************* // // * Internal Views * // // ************************************* // 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/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"; } } 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 diff --git a/kleros-sdk/package.json b/kleros-sdk/package.json index 8b29618ac..25f8014b4 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.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", diff --git a/kleros-sdk/src/utils/getDispute.ts b/kleros-sdk/src/utils/getDispute.ts index c73fa1f24..ca7a500fe 100644 --- a/kleros-sdk/src/utils/getDispute.ts +++ b/kleros-sdk/src/utils/getDispute.ts @@ -56,5 +56,16 @@ export const getDispute = async (disputeParameters: GetDisputeParameters): Promi const populatedTemplate = populateTemplate(templateData, data); + // 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 new file mode 100644 index 000000000..0d1fadc0f --- /dev/null +++ b/kleros-sdk/test/getDispute.test.ts @@ -0,0 +1,217 @@ +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 standardRefuseToArbitrateAnswer = { + id: "0x0", + title: "Refuse to Arbitrate / Invalid", + description: "Refuse to Arbitrate / Invalid", + reserved: true, + }; + + 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(standardRefuseToArbitrateAnswer); + }); + + 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(standardRefuseToArbitrateAnswer); + expect(result?.answers[1].id).toBe("0x1"); + expect(result?.answers[2].id).toBe("0x2"); + }); + + it("should overwrite existing answer with id 0x0 or 0x00", async () => { + // Test with 0x0 + const mockTemplate0x0 = { + disputeTemplate: { + templateData: JSON.stringify({ + title: "Test Dispute", + description: "Test Description", + question: "Test Question", + answers: [ + { + id: "0x0", + 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(mockTemplate0x0); + + let result = await getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }); + + expect(result?.answers).toHaveLength(2); + expect(result?.answers[0]).toEqual(standardRefuseToArbitrateAnswer); + expect(result?.answers[1].id).toBe("0x1"); + + // Test with 0x00 + const mockTemplate0x00 = { + 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(fetchDisputeTemplateFromId).mockResolvedValue(mockTemplate0x00); + + result = await getDispute({ + disputeId: mockDisputeId, + coreSubgraph: mockCoreSubgraph, + dtrSubgraph: mockDtrSubgraph, + }); + + expect(result?.answers).toHaveLength(2); + expect(result?.answers[0]).toEqual(standardRefuseToArbitrateAnswer); + 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"); + }); +}); 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"], }; 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/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/assets/svgs/mini-guides/dispute-resolver/well-done.svg b/web/src/assets/svgs/mini-guides/dispute-resolver/well-done.svg index 4e94ed36a..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,5 +1,5 @@ - + @@ -11,68 +11,70 @@ - - + + + - - + + + + - - + + - - - + + + - + - + - + - + - - + + - - + + - - + + - + - + - + - - + - + \ No newline at end of file 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 @@ - + - + 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..23ffdb1b4 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,35 @@ interface IQuery { const Context = createContext(undefined); +const executor: AsyncExecutor = async ({ document, variables, extensions }) => { + 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 batchExec = 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 }) => + batchExec({ 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/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 } } 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 } }), }); 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/hooks/useSpamEvidence.ts b/web/src/hooks/useSpamEvidence.ts new file mode 100644 index 000000000..8b420001c --- /dev/null +++ b/web/src/hooks/useSpamEvidence.ts @@ -0,0 +1,42 @@ +import { useQuery } from "@tanstack/react-query"; +import { gql, request } from "graphql-request"; + +import { isKlerosNeo, isKlerosUniversity, isTestnetDeployment } from "src/consts"; +import { isUndefined } from "src/utils"; + +const spamEvidenceQuery = gql` + query SpamEvidences($deployment: CourtV2Deployment!, $evidenceGroupId: String!) { + courtv2EvidenceSpamsByGroupId(deployment: $deployment, evidenceGroupId: $evidenceGroupId) { + evidenceIds + } + } +`; + +type SpamEvidences = { + courtv2EvidenceSpamsByGroupId: { evidenceIds: 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 = (evidenceGroupId: string) => { + const isEnabled = !isUndefined(atlasUri) && !isUndefined(evidenceGroupId); + + const variables = { deployment: getAtlasDeployment(), evidenceGroupId }; + return useQuery({ + queryKey: [`evidenceSpamQuery`], + enabled: isEnabled, + staleTime: 60000, + queryFn: async () => await request(`${atlasUri}/graphql`, spamEvidenceQuery, variables), + }); +}; 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..f5416ed1a 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 > 0 ? ( + + ) : ( + + ) + ) : ( + + )} - ) : ( - ); }; 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/Cases/CaseDetails/Evidence/index.tsx b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx index e27947f27..0a8adde07 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx @@ -8,18 +8,18 @@ import { Button } from "@kleros/ui-components-library"; import DownArrow from "svgs/icons/arrow-down.svg"; -import { spamEvidencesIds } from "consts/index"; +import { useSpamEvidence } from "hooks/useSpamEvidence"; import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery"; import { useEvidences } from "queries/useEvidences"; import { landscapeStyle } from "styles/landscapeStyle"; +import { Divider } from "components/Divider"; import EvidenceCard from "components/EvidenceCard"; import { SkeletonEvidenceCard } from "components/StyledSkeleton"; import EvidenceSearch from "./EvidenceSearch"; -import { Divider } from "components/Divider"; const Container = styled.div` width: 100%; @@ -85,6 +85,7 @@ const Evidence: React.FC = () => { const [search, setSearch] = useState(); const [debouncedSearch, setDebouncedSearch] = useState(); const [showSpam, setShowSpam] = useState(false); + const { data: spamEvidences } = useSpamEvidence(disputeData?.dispute?.externalDisputeId?.toString()); const { data } = useEvidences(disputeData?.dispute?.externalDisputeId?.toString(), debouncedSearch); @@ -99,57 +100,66 @@ const Evidence: React.FC = () => { latestEvidence.scrollIntoView({ behavior: "smooth" }); }, [ref]); + const isSpam = useCallback( + (evidenceId: string) => { + return Boolean(spamEvidences?.courtv2EvidenceSpamsByGroupId.evidenceIds?.includes(evidenceId)); + }, + [spamEvidences] + ); + 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 ( {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} ); }; -const isSpam = (id: string) => { - return spamEvidencesIds.includes(id); -}; - export default Evidence; diff --git a/web/src/pages/Courts/CourtDetails/Description.tsx b/web/src/pages/Courts/CourtDetails/Description.tsx index 235099dec..6cae460bc 100644 --- a/web/src/pages/Courts/CourtDetails/Description.tsx +++ b/web/src/pages/Courts/CourtDetails/Description.tsx @@ -17,10 +17,41 @@ const Container = styled.div` const TextContainer = styled.div` width: 100%; padding: 12px 0; +`; +const StyledReactMarkdown = styled(ReactMarkdown)` p { word-break: break-word; } + + ul, + ol { + li + li { + margin-top: 8px; + } + } + + 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 { + font-size: 16px; + } `; const StyledTabs = styled(Tabs)` @@ -35,9 +66,9 @@ const StyledTabs = styled(Tabs)` `; interface IPolicy { - description?: string; + purpose?: string; requiredSkills?: string; - summary?: string; + rules?: string; } const TABS = [ @@ -45,7 +76,7 @@ const TABS = [ text: "Purpose", value: 0, path: "purpose", - isVisible: (policy: IPolicy) => !!policy?.description, + isVisible: (policy: IPolicy) => !!policy?.purpose, }, { text: "Skills", @@ -57,7 +88,7 @@ const TABS = [ text: "Policy", value: 2, path: "policy", - isVisible: (policy: IPolicy) => !!policy?.summary, + isVisible: (policy: IPolicy) => !!policy?.rules, }, ]; @@ -88,9 +119,9 @@ const Description: React.FC = () => { - + - + 0 ? filteredTabs[0].path : ""} replace />} /> @@ -101,6 +132,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/pages/Resolver/Policy/index.tsx b/web/src/pages/Resolver/Policy/index.tsx index cee5bd0b8..66c8b5888 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/styles/global-style.ts b/web/src/styles/global-style.ts index 60e5b7f52..94edd40d4 100644 --- a/web/src/styles/global-style.ts +++ b/web/src/styles/global-style.ts @@ -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}; } } 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"; } diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts index 0bd973663..dcb9872f4 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,28 @@ 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; + const typesString = restrictions.restriction.allowedMimeTypes + .map((type) => { + const [prefix, suffix] = type.split("/"); + if (!suffix) return prefix ?? null; + + return suffix === "*" ? prefix : suffix; + }) + .join(", "); + + return `Allowed file types: [${typesString}], Max allowed size: ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} MB.`; +}; diff --git a/yarn.lock b/yarn.lock index bb0a93647..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" @@ -5448,7 +5475,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" @@ -5460,23 +5488,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" @@ -5570,6 +5598,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" @@ -5582,8 +5611,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" @@ -5605,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:^" @@ -7052,67 +7085,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 @@ -7578,14 +7611,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: @@ -8953,6 +8986,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" @@ -15785,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" @@ -16303,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" @@ -17159,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" @@ -18700,7 +18763,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: @@ -20613,16 +20691,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 @@ -20675,14 +20753,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" @@ -20696,7 +20774,7 @@ __metadata: viem: "npm:2.7.14" peerDependencies: hardhat: ^2.16.0 - checksum: 10/af8bd86400035ab1ed574c5f782e7ee408fff75ea6cfcd56ab87bf80c2e911e9fbe943b528ad17844abad8ff8f63daa24aae391ccbf3e07a9970be66f949bd3d + checksum: 10/32a13011b414bae887741907bb2ea23cda0e645fb4fdfce87bed5a37be95f26a723ccc4140b413ec215832d6cc0fa5ff517f6d47af1bbedee5508bb5432269d7 languageName: node linkType: hard @@ -20726,13 +20804,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" @@ -20784,7 +20862,7 @@ __metadata: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 10/77e3f0447e42265107242827f8efd8346224c79001a0eea2d0d71bcaeee1ab9ed1e1fff47c28e36d19e1b3c386707dc60dd3d5e22b09da15f8b2ea5feb5ff943 + checksum: 10/521d46b31d15cda4c38b4e1995ba47bd34386bd10aa0554be4e2e7c9c889d25ce57c4018c76eda94168da5bab697ab5ad98b2715a1033247a0414a8853787bdd languageName: node linkType: hard @@ -29391,15 +29469,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 @@ -34434,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