From 4e5bc8746b28de79be240ac9cfd10b660249eed0 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 27 Jan 2025 19:59:50 +0000 Subject: [PATCH 01/10] chore: change governor script --- contracts/hardhat.config.ts | 1 + contracts/scripts/changeGovernor.ts | 67 ++++++++++++++++++++++++++++ contracts/scripts/utils/contracts.ts | 65 +++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 contracts/scripts/changeGovernor.ts create mode 100644 contracts/scripts/utils/contracts.ts diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 05b505232..5639418dd 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -16,6 +16,7 @@ import "hardhat-tracer"; require("./scripts/simulations/tasks"); require("./scripts/populatePolicyRegistry"); require("./scripts/populateCourts"); +require("./scripts/changeGovernor"); dotenv.config(); diff --git a/contracts/scripts/changeGovernor.ts b/contracts/scripts/changeGovernor.ts new file mode 100644 index 000000000..710fda9f9 --- /dev/null +++ b/contracts/scripts/changeGovernor.ts @@ -0,0 +1,67 @@ +import { task } from "hardhat/config"; +import { prompt, print } from "gluegun"; +import { Cores, getContracts } from "./utils/contracts"; + +const { bold } = print.colors; + +task("change-governor", "Changes the governor for all the contracts") + .addPositionalParam("newGovernor", "The address of the new governor") + .addOptionalParam( + "coreType", + "The type of core to use between base, neo, university (default: base)", + Cores.BASE.toString() + ) + .setAction(async (taskArgs, hre) => { + const newGovernor = taskArgs.newGovernor; + print.highlight(`💣 Changing governor to ${bold(newGovernor)}`); + + const { confirm } = await prompt.ask({ + type: "confirm", + name: "confirm", + message: "Are you sure you want to proceed?", + }); + if (!confirm) { + console.log("Operation cancelled by user."); + return; + } + + let coreType = Cores.BASE; + coreType = Cores[taskArgs.coreType.toUpperCase() as keyof typeof Cores]; + if (coreType === undefined) { + console.error("Invalid core type, must be one of base, neo, university"); + return; + } + console.log("Using core type %s", Cores[coreType]); + + const { core, disputeKitClassic, disputeResolver, disputeTemplateRegistry, chainlinkRng, randomizerRng } = + await getContracts(hre, coreType); + + const updateGovernor = async (contractName: string, contractInstance: any) => { + print.info(`Changing governor for ${contractName}`); + + const spinner = print.spin(`Executing transaction for ${contractName}...`); + try { + const tx = await contractInstance.changeGovernor(newGovernor); + await tx.wait(); + spinner.succeed(`Governor changed for ${contractName}, tx hash: ${tx.hash}`); + } catch (error) { + if (error instanceof Error) { + spinner.fail(`Failed to change governor for ${contractName}: ${error.message}`); + } else { + spinner.fail(`Failed to change governor for ${contractName}: ${String(error)}`); + } + } + }; + + // TODO: upgrade and add changeGovernor! + // await updateGovernor("Sortition", sortition) + + await updateGovernor("KlerosCore", core); + await updateGovernor("DisputeKitClassic", disputeKitClassic); + await updateGovernor("DisputeResolver", disputeResolver); + await updateGovernor("DisputeTemplateRegistry", disputeTemplateRegistry); + if (chainlinkRng) await updateGovernor("ChainlinkRNG", chainlinkRng); + if (randomizerRng) await updateGovernor("RandomizerRNG", randomizerRng); + + print.success("Governor changed successfully"); + }); diff --git a/contracts/scripts/utils/contracts.ts b/contracts/scripts/utils/contracts.ts new file mode 100644 index 000000000..a22b803e3 --- /dev/null +++ b/contracts/scripts/utils/contracts.ts @@ -0,0 +1,65 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { + BlockHashRNG, + ChainlinkRNG, + DisputeKitClassic, + DisputeResolver, + DisputeTemplateRegistry, + KlerosCore, + KlerosCoreNeo, + PNK, + RandomizerRNG, + SortitionModule, + SortitionModuleNeo, + TransactionBatcher, +} from "../../typechain-types"; + +export enum Cores { + BASE, + NEO, + UNIVERSITY, +} + +export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cores) => { + const { ethers } = hre; + let core: KlerosCore | KlerosCoreNeo; + let sortition: SortitionModule | SortitionModuleNeo; + let disputeKitClassic: DisputeKitClassic; + let disputeResolver: DisputeResolver; + switch (coreType) { + case Cores.NEO: + core = (await ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo; + sortition = (await ethers.getContract("SortitionModuleNeo")) as SortitionModuleNeo; + disputeKitClassic = (await ethers.getContract("DisputeKitClassicNeo")) as DisputeKitClassic; + disputeResolver = (await ethers.getContract("DisputeResolverNeo")) as DisputeResolver; + break; + case Cores.BASE: + core = (await ethers.getContract("KlerosCore")) as KlerosCore; + sortition = (await ethers.getContract("SortitionModule")) as SortitionModule; + disputeKitClassic = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic; + disputeResolver = (await ethers.getContract("DisputeResolver")) as DisputeResolver; + break; + case Cores.UNIVERSITY: + throw new Error("University core is not supported"); + default: + throw new Error("Invalid core type, must be one of base, neo"); + } + const disputeTemplateRegistry = (await ethers.getContract("DisputeTemplateRegistry")) as DisputeTemplateRegistry; + const batcher = (await ethers.getContract("TransactionBatcher")) as TransactionBatcher; + const chainlinkRng = await ethers.getContractOrNull("ChainlinkRNG"); + const randomizerRng = await ethers.getContractOrNull("RandomizerRNG"); + const blockHashRNG = await ethers.getContractOrNull("BlockHashRNG"); + const pnk = (await ethers.getContract("PNK")) as PNK; + return { + core, + sortition, + disputeKitClassic, + disputeResolver, + disputeTemplateRegistry, + chainlinkRng, + randomizerRng, + blockHashRNG, + pnk, + batcher, + }; +}; From 2bbb88f2ded25eca4c0b0a5c4b66843c2fbd4f61 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 27 Jan 2025 20:00:17 +0000 Subject: [PATCH 02/10] fix: policy script, names --- contracts/config/policies.v2.mainnet-neo.json | 20 +++++++++---------- contracts/package.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/config/policies.v2.mainnet-neo.json b/contracts/config/policies.v2.mainnet-neo.json index 9f569b3ac..ffcbb4622 100644 --- a/contracts/config/policies.v2.mainnet-neo.json +++ b/contracts/config/policies.v2.mainnet-neo.json @@ -181,36 +181,36 @@ "uri": "/ipfs/QmcaMbPgKAAvc67URzbq1yegnCANPRSNSmLQ7GwsyYNTCe" }, { - "name": "Humanity Court", + "name": "Humanity", "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/QmXAVumYfMmezMQSbhYn33iCFxwqLguRztz7HcJaLnX1Z4" + "uri": "/ipfs/QmfH68LJWRQ7UEJqFGDKDpR6hmxmmJrbz2EHJMgqtCgFo6" }, { - "name": "Development Court", + "name": "Development", "purpose": "In this court, jurors will solve disputes involving the respect of specifications given by the client.", "rules": "### Example\nDeveloper does not respect indentation, does not name variables explicitly or has not made a clear file structure. In such cases, jurors should refuse the proposal made by the developer.", "requiredSkills": "This court requires a good level of familiarity with programming. Jurors who are not intermediate developers are advised to stake into this court only if they have some basics of low-level programming languages, ​​algorithmic and knowledge of good practices of development.", "court": 25, - "uri": "/ipfs/QmfH2k1PmX4YufdZoAKwoGtdbjNZaxaTPdXB2uAs3rQsjh" + "uri": "/ipfs/QmdiQGftN4Mxtocvf1ENxeEvVzU62AGR3knzfhMDb85iTh" }, { - "name": "Solidity Court", + "name": "Solidity", "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/QmPRckaaNLj9ycZH6otChTwbkDsBnhkNrXnarF5vD6rXKy" + "uri": "/ipfs/QmbKfy5vF5jZ5GFqFKgUxnYsbAjJdtsDfp2UJLwxzDokmb" }, { - "name": "Javascript Court", + "name": "Javascript", "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/QmS9JzVezbAioSXXcuQsMw31pNjg5jeaV8vtbpwY5cMG8b" + "uri": "/ipfs/Qmaf4NzAvyVa4biu7MwaGTTwCe46XVSdBa3t3Uu2soFToz" }, { "name": "Corte de Curación en Español", @@ -229,11 +229,11 @@ "uri": "/ipfs/Qmczrn2DgdKGnacdvKRYwCk7JkeyTCokdqQycWdetYrxGC" }, { - "name": "Oracle Court", + "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": 30, - "uri": "/ipfs/QmVFKNM1F3YnH2DVFh1Xd6epL9Asum2xBm9kGUQeXypAN5" + "uri": "/ipfs/QmZqV3TJNZtYTZ74fcVTNT5uEwrsv2aDkGGVB5XUS32VD9" } ] diff --git a/contracts/package.json b/contracts/package.json index 89adff2e7..f2290f059 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -58,7 +58,7 @@ "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", + "populate:policies:mainnetNeo": "hardhat populate:policy-registry --from v2_mainnet_neo --network arbitrum", "release:patch": "scripts/publish.sh patch", "release:minor": "scripts/publish.sh minor", "release:major": "scripts/publish.sh major", From 4b3b100ef44da191fb80cc8c53f3eae81fcc4450 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 27 Jan 2025 22:17:59 +0000 Subject: [PATCH 03/10] fix: bad practice --- contracts/scripts/keeperBot.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index 8bf13e339..59d9a92c6 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -544,7 +544,7 @@ async function main() { // Skip some disputes disputes = filterDisputesToSkip(disputes); disputesWithoutJurors = filterDisputesToSkip(disputesWithoutJurors); - for (var dispute of disputes) { + for (const dispute of disputes) { logger.info(`Dispute #${dispute.id}, round #${dispute.currentRoundIndex}, ${dispute.period} period`); } logger.info(`Disputes needing more jurors: ${disputesWithoutJurors.map((dispute) => dispute.id)}`); @@ -606,7 +606,7 @@ async function main() { logger.info(`Current phase: ${PHASES[getNumber(await sortition.phase())]}`); - for (var dispute of disputes) { + for (const dispute of disputes) { // ----------------------------------------------- // // PASS PERIOD // // ----------------------------------------------- // @@ -631,7 +631,7 @@ async function main() { ); logger.info(`Disputes not fully executed: ${unprocessedDisputesInExecution.map((dispute) => dispute.id)}`); - for (var dispute of unprocessedDisputesInExecution) { + for (const dispute of unprocessedDisputesInExecution) { const { period } = await core.disputes(dispute.id); if (period !== 4n) { logger.info(`Skipping dispute #${dispute.id} because it is not in the execution period`); From ae8f482cfa7941e27e63c60de71f27232fcf7069 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 4 Feb 2025 00:05:08 +0000 Subject: [PATCH 04/10] chore: automated curation court config --- contracts/config/courts.v2.devnet.json | 16 ++++++++++++++++ contracts/config/courts.v2.mainnet-neo.json | 16 ++++++++++++++++ contracts/config/policies.v2.devnet.json | 8 ++++++++ contracts/config/policies.v2.mainnet-neo.json | 8 ++++++++ 4 files changed, 48 insertions(+) diff --git a/contracts/config/courts.v2.devnet.json b/contracts/config/courts.v2.devnet.json index 806e8451d..69a8a591f 100644 --- a/contracts/config/courts.v2.devnet.json +++ b/contracts/config/courts.v2.devnet.json @@ -78,5 +78,21 @@ 240, 600 ] + }, + { + "name": "Automated Curation", + "id": 6, + "parent": 2, + "hiddenVotes": false, + "minStake": "2000000000000000000", + "alpha": "3100", + "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 16e657873..0c5fa82f6 100644 --- a/contracts/config/courts.v2.mainnet-neo.json +++ b/contracts/config/courts.v2.mainnet-neo.json @@ -478,5 +478,21 @@ 583200, 388800 ] + }, + { + "name": "Automated Curation", + "id": 31, + "parent": 10, + "hiddenVotes": false, + "minStake": "2600000000000000000000", + "alpha": "290", + "feeForJuror": "170000000000000", + "jurorsForCourtJump": "3", + "timesPerPeriod": [ + 140400, + 291600, + 291600, + 194400 + ] } ] diff --git a/contracts/config/policies.v2.devnet.json b/contracts/config/policies.v2.devnet.json index 1fd7918e2..811587df6 100644 --- a/contracts/config/policies.v2.devnet.json +++ b/contracts/config/policies.v2.devnet.json @@ -37,5 +37,13 @@ "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" + }, + { + "name": "Automated Curation", + "purpose": "The Automated Curation Court is designed to handle micro-tasks and cases requiring fast and near-instant resolution. These include, but are not limited to, content moderation, gaming disputes, automated data curation, and similar use cases. AI agents capable of rapid decision-making are better suited for this court's short resolution time.", + "rules": "", + "requiredSkills": "AI agents participating as jurors of this court must be capable of:\n- Data Processing Efficiency: Handling high volumes of disputes in near real-time without compromising accuracy.\n- Kleros Rules Compliance: analyzing all evidence presented and ruling in accordance with Kleros General Court Policy, this Automated Curation Court Policy, and the case’s Primary Document.\n- Temporal Awareness: Identifying the moment of relevant events in a dispute, which may be crucial for applying the General Court’s Policy. This includes understanding the state of the world at the time the dispute was created, recognizing the exact time a piece of evidence was submitted, understanding the applicable court policies and arbitrable application primary documents that existed at the time of the dispute’s creation, and assessing whether a piece of evidence was submitted after the end of the evidence period of the initial round of the dispute.", + "court": 6, + "uri": "/ipfs/QmNm6w4itnvMoWQXcz3CAQmjSF4nP5w6uTwGAQ1Z5YoUKJ" } ] diff --git a/contracts/config/policies.v2.mainnet-neo.json b/contracts/config/policies.v2.mainnet-neo.json index ffcbb4622..a820d53d1 100644 --- a/contracts/config/policies.v2.mainnet-neo.json +++ b/contracts/config/policies.v2.mainnet-neo.json @@ -235,5 +235,13 @@ "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/QmZqV3TJNZtYTZ74fcVTNT5uEwrsv2aDkGGVB5XUS32VD9" + }, + { + "name": "Automated Curation", + "purpose": "The Automated Curation Court is designed to handle micro-tasks and cases requiring fast and near-instant resolution. These include, but are not limited to, content moderation, gaming disputes, automated data curation, and similar use cases. AI agents capable of rapid decision-making are better suited for this court's short resolution time.", + "rules": "", + "requiredSkills": "AI agents participating as jurors of this court must be capable of:\n- Data Processing Efficiency: Handling high volumes of disputes in near real-time without compromising accuracy.\n- Kleros Rules Compliance: analyzing all evidence presented and ruling in accordance with Kleros General Court Policy, this Automated Curation Court Policy, and the case’s Primary Document.\n- Temporal Awareness: Identifying the moment of relevant events in a dispute, which may be crucial for applying the General Court’s Policy. This includes understanding the state of the world at the time the dispute was created, recognizing the exact time a piece of evidence was submitted, understanding the applicable court policies and arbitrable application primary documents that existed at the time of the dispute’s creation, and assessing whether a piece of evidence was submitted after the end of the evidence period of the initial round of the dispute.", + "court": 31, + "uri": "/ipfs/QmV4TYUwUFgpMMKfWgDQjBtC5Dn5cRGraDnVatccSb6LMx" } ] From e52bc67e2a749c270c1d813a2a1ba5fcde7772e8 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 4 Feb 2025 00:08:37 +0000 Subject: [PATCH 05/10] chore: scripts handling of the governor being a contract --- contracts/scripts/changeGovernor.ts | 25 ++--- contracts/scripts/populateCourts.ts | 101 ++++++++------------ contracts/scripts/populatePolicyRegistry.ts | 3 +- contracts/scripts/utils/contracts.ts | 31 ++++-- contracts/scripts/utils/execution.ts | 34 +++++++ 5 files changed, 113 insertions(+), 81 deletions(-) create mode 100644 contracts/scripts/utils/execution.ts diff --git a/contracts/scripts/changeGovernor.ts b/contracts/scripts/changeGovernor.ts index 710fda9f9..780dd6ba7 100644 --- a/contracts/scripts/changeGovernor.ts +++ b/contracts/scripts/changeGovernor.ts @@ -6,11 +6,7 @@ const { bold } = print.colors; task("change-governor", "Changes the governor for all the contracts") .addPositionalParam("newGovernor", "The address of the new governor") - .addOptionalParam( - "coreType", - "The type of core to use between base, neo, university (default: base)", - Cores.BASE.toString() - ) + .addOptionalParam("coreType", "The type of core to use between base, neo, university (default: base)", Cores.BASE) .setAction(async (taskArgs, hre) => { const newGovernor = taskArgs.newGovernor; print.highlight(`💣 Changing governor to ${bold(newGovernor)}`); @@ -25,16 +21,22 @@ task("change-governor", "Changes the governor for all the contracts") return; } - let coreType = Cores.BASE; - coreType = Cores[taskArgs.coreType.toUpperCase() as keyof typeof Cores]; + const coreType = Cores[taskArgs.coreType.toUpperCase() as keyof typeof Cores]; if (coreType === undefined) { console.error("Invalid core type, must be one of base, neo, university"); return; } - console.log("Using core type %s", Cores[coreType]); + console.log("Using core type %s", coreType); - const { core, disputeKitClassic, disputeResolver, disputeTemplateRegistry, chainlinkRng, randomizerRng } = - await getContracts(hre, coreType); + const { + core, + disputeKitClassic, + disputeResolver, + disputeTemplateRegistry, + policyRegistry, + chainlinkRng, + randomizerRng, + } = await getContracts(hre, coreType); const updateGovernor = async (contractName: string, contractInstance: any) => { print.info(`Changing governor for ${contractName}`); @@ -54,12 +56,13 @@ task("change-governor", "Changes the governor for all the contracts") }; // TODO: upgrade and add changeGovernor! - // await updateGovernor("Sortition", sortition) + // await updateGovernor("SortitionModule", sortition) await updateGovernor("KlerosCore", core); await updateGovernor("DisputeKitClassic", disputeKitClassic); await updateGovernor("DisputeResolver", disputeResolver); await updateGovernor("DisputeTemplateRegistry", disputeTemplateRegistry); + await updateGovernor("PolicyRegistry", policyRegistry); if (chainlinkRng) await updateGovernor("ChainlinkRNG", chainlinkRng); if (randomizerRng) await updateGovernor("RandomizerRNG", randomizerRng); diff --git a/contracts/scripts/populateCourts.ts b/contracts/scripts/populateCourts.ts index 5b26ceb4b..739debe78 100644 --- a/contracts/scripts/populateCourts.ts +++ b/contracts/scripts/populateCourts.ts @@ -7,6 +7,8 @@ import courtsV2ArbitrumTestnet from "../config/courts.v2.testnet.json"; import courtsV2ArbitrumDevnet from "../config/courts.v2.devnet.json"; import courtsV2MainnetNeo from "../config/courts.v2.mainnet-neo.json"; import { isDevnet } from "../deploy/utils"; +import { execute } from "./utils/execution"; +import { getContracts, Cores } from "./utils/contracts"; enum HomeChains { ARBITRUM_ONE = 42161, @@ -22,12 +24,6 @@ enum Sources { V2_MAINNET_NEO, } -enum Cores { - BASE, - NEO, - UNIVERSITY, -} - type Court = { id: number; parent: number; @@ -57,11 +53,7 @@ task("populate:courts", "Populates the courts and their parameters") undefined, types.int ) - .addOptionalParam( - "coreType", - "The type of core to use between base, neo, university (default: base)", - Cores.BASE.toString() - ) + .addOptionalParam("coreType", "The type of core to use between base, neo, university (default: base)", Cores.BASE) .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) => { @@ -90,13 +82,12 @@ task("populate:courts", "Populates the courts and their parameters") } console.log("Populating from source %s", Sources[from]); - let coreType = Cores.BASE; - coreType = Cores[taskArgs.coreType.toUpperCase() as keyof typeof Cores]; + const coreType = Cores[taskArgs.coreType.toUpperCase() as keyof typeof Cores]; if (coreType === undefined) { console.error("Invalid core type, must be one of base, neo, university"); return; } - console.log("Using core type %s", Cores[coreType]); + console.log("Using core type %s", coreType); const truncateWei = (x: bigint) => (x / TEN_THOUSAND_GWEI) * TEN_THOUSAND_GWEI; @@ -163,21 +154,7 @@ task("populate:courts", "Populates the courts and their parameters") console.log("courtsV2 = %O", courtsV2); - let core: KlerosCore | KlerosCoreNeo | KlerosCoreUniversity; - switch (coreType) { - case Cores.UNIVERSITY: - console.log("Using KlerosCoreUniversity"); - core = (await ethers.getContract("KlerosCoreUniversity")) as KlerosCoreUniversity; - break; - case Cores.NEO: - console.log("Using KlerosCoreNeo"); - core = (await ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo; - break; - default: - console.log("Using KlerosCore"); - core = (await ethers.getContract("KlerosCore")) as KlerosCore; - break; - } + const { core } = await getContracts(hre, coreType); for (const court of courtsV2) { const courtPresent = await core.courts(court.id).catch(() => {}); @@ -244,43 +221,49 @@ task("populate:courts", "Populates the courts and their parameters") continue; } try { - await core.changeCourtParameters( - court.id, - court.hiddenVotes, - court.minStake, - court.alpha, - court.feeForJuror, - court.jurorsForCourtJump, - [court.timesPerPeriod[0], court.timesPerPeriod[1], court.timesPerPeriod[2], court.timesPerPeriod[3]] - ); + await core.changeCourtParameters + .populateTransaction( + court.id, + court.hiddenVotes, + court.minStake, + court.alpha, + court.feeForJuror, + court.jurorsForCourtJump, + [court.timesPerPeriod[0], court.timesPerPeriod[1], court.timesPerPeriod[2], court.timesPerPeriod[3]] + ) + .then(execute); } catch (error) { console.error("Error changing court parameters: %s", error); } } else { console.log("Court %d not found, creating it with", court.id, court); if (coreType === Cores.UNIVERSITY) { - await (core as KlerosCoreUniversity).createCourt( - court.parent, - court.hiddenVotes, - court.minStake, - court.alpha, - court.feeForJuror, - court.jurorsForCourtJump, - [court.timesPerPeriod[0], court.timesPerPeriod[1], court.timesPerPeriod[2], court.timesPerPeriod[3]], - [DISPUTE_KIT_CLASSIC] - ); + await (core as KlerosCoreUniversity).createCourt + .populateTransaction( + court.parent, + court.hiddenVotes, + court.minStake, + court.alpha, + court.feeForJuror, + court.jurorsForCourtJump, + [court.timesPerPeriod[0], court.timesPerPeriod[1], court.timesPerPeriod[2], court.timesPerPeriod[3]], + [DISPUTE_KIT_CLASSIC] + ) + .then(execute); } else { - await (core as KlerosCore).createCourt( - court.parent, - court.hiddenVotes, - court.minStake, - court.alpha, - court.feeForJuror, - court.jurorsForCourtJump, - [court.timesPerPeriod[0], court.timesPerPeriod[1], court.timesPerPeriod[2], court.timesPerPeriod[3]], - ethers.toBeHex(5), // Not accessible on-chain, but has always been set to the same value so far. - [DISPUTE_KIT_CLASSIC] - ); + await (core as KlerosCore).createCourt + .populateTransaction( + court.parent, + court.hiddenVotes, + court.minStake, + court.alpha, + court.feeForJuror, + court.jurorsForCourtJump, + [court.timesPerPeriod[0], court.timesPerPeriod[1], court.timesPerPeriod[2], court.timesPerPeriod[3]], + ethers.toBeHex(5), // Not accessible on-chain, but has always been set to the same value so far. + [DISPUTE_KIT_CLASSIC] + ) + .then(execute); } } diff --git a/contracts/scripts/populatePolicyRegistry.ts b/contracts/scripts/populatePolicyRegistry.ts index 0961c280b..de119723a 100644 --- a/contracts/scripts/populatePolicyRegistry.ts +++ b/contracts/scripts/populatePolicyRegistry.ts @@ -6,6 +6,7 @@ import policiesV2ArbitrumTestnet from "../config/policies.v2.testnet.json"; import policiesV2ArbitrumDevnet from "../config/policies.v2.devnet.json"; import policiesV2MainnetNeo from "../config/policies.v2.mainnet-neo.json"; import { isDevnet } from "../deploy/utils"; +import { execute } from "./utils/execution"; enum HomeChains { ARBITRUM_ONE = 42161, @@ -109,6 +110,6 @@ task("populate:policy-registry", "Populates the policy registry for each court") for await (const policy of policiesV2) { console.log("Populating policy for %s Court (%d): %s", policy.name, policy.court, policy.uri); - await policyRegistry.setPolicy(policy.court, policy.name, policy.uri); + await policyRegistry.setPolicy.populateTransaction(policy.court, policy.name, policy.uri).then(execute); } }); diff --git a/contracts/scripts/utils/contracts.ts b/contracts/scripts/utils/contracts.ts index a22b803e3..9298bad1d 100644 --- a/contracts/scripts/utils/contracts.ts +++ b/contracts/scripts/utils/contracts.ts @@ -7,23 +7,28 @@ import { DisputeTemplateRegistry, KlerosCore, KlerosCoreNeo, + KlerosCoreUniversity, PNK, + PolicyRegistry, RandomizerRNG, SortitionModule, SortitionModuleNeo, + SortitionModuleUniversity, TransactionBatcher, } from "../../typechain-types"; -export enum Cores { - BASE, - NEO, - UNIVERSITY, -} +export const Cores = { + BASE: "BASE", + NEO: "NEO", + UNIVERSITY: "UNIVERSITY", +} as const; -export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cores) => { +export type Core = (typeof Cores)[keyof typeof Cores]; + +export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Core) => { const { ethers } = hre; - let core: KlerosCore | KlerosCoreNeo; - let sortition: SortitionModule | SortitionModuleNeo; + let core: KlerosCore | KlerosCoreNeo | KlerosCoreUniversity; + let sortition: SortitionModule | SortitionModuleNeo | SortitionModuleUniversity; let disputeKitClassic: DisputeKitClassic; let disputeResolver: DisputeResolver; switch (coreType) { @@ -40,11 +45,16 @@ export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cor disputeResolver = (await ethers.getContract("DisputeResolver")) as DisputeResolver; break; case Cores.UNIVERSITY: - throw new Error("University core is not supported"); + core = (await ethers.getContract("KlerosCoreUniversity")) as KlerosCoreUniversity; + sortition = (await ethers.getContract("SortitionModuleUniversity")) as SortitionModuleUniversity; + disputeKitClassic = (await ethers.getContract("DisputeKitClassicUniversity")) as DisputeKitClassic; + disputeResolver = (await ethers.getContract("DisputeResolverUniversity")) as DisputeResolver; + break; default: - throw new Error("Invalid core type, must be one of base, neo"); + throw new Error("Invalid core type, must be one of BASE, NEO, or UNIVERSITY"); } const disputeTemplateRegistry = (await ethers.getContract("DisputeTemplateRegistry")) as DisputeTemplateRegistry; + const policyRegistry = (await ethers.getContract("PolicyRegistry")) as PolicyRegistry; const batcher = (await ethers.getContract("TransactionBatcher")) as TransactionBatcher; const chainlinkRng = await ethers.getContractOrNull("ChainlinkRNG"); const randomizerRng = await ethers.getContractOrNull("RandomizerRNG"); @@ -56,6 +66,7 @@ export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cor disputeKitClassic, disputeResolver, disputeTemplateRegistry, + policyRegistry, chainlinkRng, randomizerRng, blockHashRNG, diff --git a/contracts/scripts/utils/execution.ts b/contracts/scripts/utils/execution.ts new file mode 100644 index 000000000..510bb03d5 --- /dev/null +++ b/contracts/scripts/utils/execution.ts @@ -0,0 +1,34 @@ +import { type ContractTransaction } from "ethers"; + +const governableAbi = [ + { + inputs: [], + name: "governor", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +]; + +export const execute = async (tx: ContractTransaction) => { + const hre = require("hardhat"); + const { ethers } = hre; + + const contract = await ethers.getContractAt(governableAbi, tx.to); + const governor = await contract.governor(); + const isContract = (await ethers.provider.getCode(governor)).length > 2; + if (isContract) { + // Don't execute, just log the tx. It must be submitted for execution separately. + console.log("tx = %O", tx); + } else { + // Execute the tx + const signer = (await ethers.getSigners())[0]; + await signer.sendTransaction(tx); + } +}; From 708f000c9fd1834a643c01591bc277d4bbc937bd Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 5 Feb 2025 19:16:04 +0000 Subject: [PATCH 06/10] chore: getDisputeTemplate task --- contracts/hardhat.config.ts | 1 + contracts/scripts/getDisputeTemplate.ts | 31 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 contracts/scripts/getDisputeTemplate.ts diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 5639418dd..1e6c8b4bf 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -17,6 +17,7 @@ require("./scripts/simulations/tasks"); require("./scripts/populatePolicyRegistry"); require("./scripts/populateCourts"); require("./scripts/changeGovernor"); +require("./scripts/getDisputeTemplate"); dotenv.config(); diff --git a/contracts/scripts/getDisputeTemplate.ts b/contracts/scripts/getDisputeTemplate.ts new file mode 100644 index 000000000..d036f2ac3 --- /dev/null +++ b/contracts/scripts/getDisputeTemplate.ts @@ -0,0 +1,31 @@ +import { task } from "hardhat/config"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { IDisputeTemplateRegistry } from "../typechain-types"; + +task("get-dispute-template", "Gets a dispute template by ID") + .addPositionalParam("templateId", "The ID of the template to query") + .setAction(async function ({ templateId }: { templateId: string }, hre: HardhatRuntimeEnvironment) { + const { ethers } = hre; + + // Get the contract instance + const disputeTemplateRegistry = await ethers.getContract("DisputeTemplateRegistry"); + + // Query the events + const filter = disputeTemplateRegistry.filters.DisputeTemplate(BigInt(templateId)); + const events = await disputeTemplateRegistry.queryFilter(filter); + + if (events.length === 0) { + console.log(`No template found with ID ${templateId}`); + return; + } + + // Get the most recent event + const event = events[events.length - 1]; + + console.log("Template Details:"); + console.log("----------------"); + console.log(`Template ID: ${event.args._templateId}`); + console.log(`Template Tag: ${event.args._templateTag}`); + console.log(`Template Data: ${event.args._templateData}`); + console.log(`Template Data Mappings: ${event.args._templateDataMappings}`); + }); From 245f07df6a573bc447107443ea09f7594287df01 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 5 Feb 2025 20:49:35 +0000 Subject: [PATCH 07/10] chore: scripts support for KlerosCoreSnapshotProxy --- contracts/scripts/changeGovernor.ts | 2 ++ contracts/scripts/utils/contracts.ts | 35 +++++++++++++++------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/contracts/scripts/changeGovernor.ts b/contracts/scripts/changeGovernor.ts index 780dd6ba7..7d166727a 100644 --- a/contracts/scripts/changeGovernor.ts +++ b/contracts/scripts/changeGovernor.ts @@ -36,6 +36,7 @@ task("change-governor", "Changes the governor for all the contracts") policyRegistry, chainlinkRng, randomizerRng, + snapshotProxy, } = await getContracts(hre, coreType); const updateGovernor = async (contractName: string, contractInstance: any) => { @@ -63,6 +64,7 @@ task("change-governor", "Changes the governor for all the contracts") await updateGovernor("DisputeResolver", disputeResolver); await updateGovernor("DisputeTemplateRegistry", disputeTemplateRegistry); await updateGovernor("PolicyRegistry", policyRegistry); + await updateGovernor("KlerosCoreSnapshotProxy", snapshotProxy); if (chainlinkRng) await updateGovernor("ChainlinkRNG", chainlinkRng); if (randomizerRng) await updateGovernor("RandomizerRNG", randomizerRng); diff --git a/contracts/scripts/utils/contracts.ts b/contracts/scripts/utils/contracts.ts index 9298bad1d..2decbb282 100644 --- a/contracts/scripts/utils/contracts.ts +++ b/contracts/scripts/utils/contracts.ts @@ -15,6 +15,7 @@ import { SortitionModuleNeo, SortitionModuleUniversity, TransactionBatcher, + KlerosCoreSnapshotProxy, } from "../../typechain-types"; export const Cores = { @@ -33,33 +34,34 @@ export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cor let disputeResolver: DisputeResolver; switch (coreType) { case Cores.NEO: - core = (await ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo; - sortition = (await ethers.getContract("SortitionModuleNeo")) as SortitionModuleNeo; - disputeKitClassic = (await ethers.getContract("DisputeKitClassicNeo")) as DisputeKitClassic; - disputeResolver = (await ethers.getContract("DisputeResolverNeo")) as DisputeResolver; + core = await ethers.getContract("KlerosCoreNeo"); + sortition = await ethers.getContract("SortitionModuleNeo"); + disputeKitClassic = await ethers.getContract("DisputeKitClassicNeo"); + disputeResolver = await ethers.getContract("DisputeResolverNeo"); break; case Cores.BASE: - core = (await ethers.getContract("KlerosCore")) as KlerosCore; - sortition = (await ethers.getContract("SortitionModule")) as SortitionModule; - disputeKitClassic = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic; - disputeResolver = (await ethers.getContract("DisputeResolver")) as DisputeResolver; + core = await ethers.getContract("KlerosCore"); + sortition = await ethers.getContract("SortitionModule"); + disputeKitClassic = await ethers.getContract("DisputeKitClassic"); + disputeResolver = await ethers.getContract("DisputeResolver"); break; case Cores.UNIVERSITY: - core = (await ethers.getContract("KlerosCoreUniversity")) as KlerosCoreUniversity; - sortition = (await ethers.getContract("SortitionModuleUniversity")) as SortitionModuleUniversity; - disputeKitClassic = (await ethers.getContract("DisputeKitClassicUniversity")) as DisputeKitClassic; - disputeResolver = (await ethers.getContract("DisputeResolverUniversity")) as DisputeResolver; + core = await ethers.getContract("KlerosCoreUniversity"); + sortition = await ethers.getContract("SortitionModuleUniversity"); + disputeKitClassic = await ethers.getContract("DisputeKitClassicUniversity"); + disputeResolver = await ethers.getContract("DisputeResolverUniversity"); break; default: throw new Error("Invalid core type, must be one of BASE, NEO, or UNIVERSITY"); } - const disputeTemplateRegistry = (await ethers.getContract("DisputeTemplateRegistry")) as DisputeTemplateRegistry; - const policyRegistry = (await ethers.getContract("PolicyRegistry")) as PolicyRegistry; - const batcher = (await ethers.getContract("TransactionBatcher")) as TransactionBatcher; + const disputeTemplateRegistry = await ethers.getContract("DisputeTemplateRegistry"); + const policyRegistry = await ethers.getContract("PolicyRegistry"); + const batcher = await ethers.getContract("TransactionBatcher"); const chainlinkRng = await ethers.getContractOrNull("ChainlinkRNG"); const randomizerRng = await ethers.getContractOrNull("RandomizerRNG"); const blockHashRNG = await ethers.getContractOrNull("BlockHashRNG"); - const pnk = (await ethers.getContract("PNK")) as PNK; + const pnk = await ethers.getContract("PNK"); + const snapshotProxy = await ethers.getContractOrNull("KlerosCoreSnapshotProxy"); return { core, sortition, @@ -72,5 +74,6 @@ export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cor blockHashRNG, pnk, batcher, + snapshotProxy, }; }; From ae5179c31ac3b386e6e22fa78ef043b922968b8e Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 18 Feb 2025 02:16:44 +0000 Subject: [PATCH 08/10] feat: generate a tx batch file for the transaction builder Safe app --- contracts/scripts/populateCourts.ts | 4 +- contracts/scripts/populatePolicyRegistry.ts | 4 +- contracts/scripts/utils/execution.ts | 24 +++++++++++ contracts/scripts/utils/tx-builder.ts | 44 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 contracts/scripts/utils/tx-builder.ts diff --git a/contracts/scripts/populateCourts.ts b/contracts/scripts/populateCourts.ts index 739debe78..9423f9ae0 100644 --- a/contracts/scripts/populateCourts.ts +++ b/contracts/scripts/populateCourts.ts @@ -7,7 +7,7 @@ import courtsV2ArbitrumTestnet from "../config/courts.v2.testnet.json"; import courtsV2ArbitrumDevnet from "../config/courts.v2.devnet.json"; import courtsV2MainnetNeo from "../config/courts.v2.mainnet-neo.json"; import { isDevnet } from "../deploy/utils"; -import { execute } from "./utils/execution"; +import { execute, writeTransactionBatch } from "./utils/execution"; import { getContracts, Cores } from "./utils/contracts"; enum HomeChains { @@ -269,4 +269,6 @@ task("populate:courts", "Populates the courts and their parameters") await new Promise((resolve) => setTimeout(resolve, 100)); } + + writeTransactionBatch({ name: "populate-courts" }); }); diff --git a/contracts/scripts/populatePolicyRegistry.ts b/contracts/scripts/populatePolicyRegistry.ts index de119723a..dc94a5bd8 100644 --- a/contracts/scripts/populatePolicyRegistry.ts +++ b/contracts/scripts/populatePolicyRegistry.ts @@ -6,7 +6,7 @@ import policiesV2ArbitrumTestnet from "../config/policies.v2.testnet.json"; import policiesV2ArbitrumDevnet from "../config/policies.v2.devnet.json"; import policiesV2MainnetNeo from "../config/policies.v2.mainnet-neo.json"; import { isDevnet } from "../deploy/utils"; -import { execute } from "./utils/execution"; +import { execute, writeTransactionBatch } from "./utils/execution"; enum HomeChains { ARBITRUM_ONE = 42161, @@ -112,4 +112,6 @@ task("populate:policy-registry", "Populates the policy registry for each court") console.log("Populating policy for %s Court (%d): %s", policy.name, policy.court, policy.uri); await policyRegistry.setPolicy.populateTransaction(policy.court, policy.name, policy.uri).then(execute); } + + writeTransactionBatch({ name: "populate-policy-registry" }); }); diff --git a/contracts/scripts/utils/execution.ts b/contracts/scripts/utils/execution.ts index 510bb03d5..37c56d5ee 100644 --- a/contracts/scripts/utils/execution.ts +++ b/contracts/scripts/utils/execution.ts @@ -1,4 +1,6 @@ import { type ContractTransaction } from "ethers"; +import fs from "fs"; +import { type BuilderTransaction, template, transaction, transactionBuilderUrl } from "./tx-builder"; const governableAbi = [ { @@ -16,6 +18,8 @@ const governableAbi = [ }, ]; +const transactions: BuilderTransaction[] = []; + export const execute = async (tx: ContractTransaction) => { const hre = require("hardhat"); const { ethers } = hre; @@ -25,6 +29,8 @@ export const execute = async (tx: ContractTransaction) => { const isContract = (await ethers.provider.getCode(governor)).length > 2; if (isContract) { // Don't execute, just log the tx. It must be submitted for execution separately. + const { to, value, data } = tx; + transactions.push(transaction({ to, value, data })); console.log("tx = %O", tx); } else { // Execute the tx @@ -32,3 +38,21 @@ export const execute = async (tx: ContractTransaction) => { await signer.sendTransaction(tx); } }; + +export function writeTransactionBatch({ name, outputPath = "tx-batch.json" }: { name: string; outputPath?: string }) { + if (!name?.trim()) throw new Error("Batch name is required"); + + if (!transactions?.length) { + console.log("No transaction batch to write"); + return; + } + + try { + const templateObject = template({ name, transactions }); + fs.writeFileSync(outputPath, JSON.stringify(templateObject, null, 2)); + console.log(`Transaction batch written to ${outputPath}`); + console.log(`The batch can be submitted to the Safe app at: ${transactionBuilderUrl}`); + } catch (error) { + throw new Error(`Failed to write transaction batch: ${(error as Error).message}`); + } +} diff --git a/contracts/scripts/utils/tx-builder.ts b/contracts/scripts/utils/tx-builder.ts new file mode 100644 index 000000000..c401952ac --- /dev/null +++ b/contracts/scripts/utils/tx-builder.ts @@ -0,0 +1,44 @@ +// Transaction batch example: https://github.com/safe-global/safe-wallet-monorepo/blob/8bbf3b82edc347b70a038629cd9afd45eb1ed38a/apps/web/cypress/fixtures/test-working-batch.json + +const governor = "0x66e8DE9B42308c6Ca913D1EE041d6F6fD037A57e"; +const deployer = "0xf1C7c037891525E360C59f708739Ac09A7670c59"; + +export const template = ({ name, transactions }: { name: string; transactions: BuilderTransaction[] }) => ({ + version: "1.0", + chainId: "42161", // Arbitrum One + createdAt: Date.now(), + meta: { + name, + description: "", // Not used because the Safe app doesn't show it + txBuilderVersion: "1.18.0", + createdFromSafeAddress: governor, + createdFromOwnerAddress: deployer, + }, + transactions, +}); + +export const transaction = ({ + to, + value, + data, +}: { + to: string; + value: bigint | undefined; + data: string; +}): BuilderTransaction => ({ + to, + value: value?.toString() ?? "0", + data, + contractMethod: null, + contractInputsValues: null, +}); + +export interface BuilderTransaction { + to: string; + value: string; + data: string; + contractMethod: null; + contractInputsValues: null; +} + +export const transactionBuilderUrl = `https://app.safe.global/apps/open?safe=arb1:${governor}&appUrl=https%3A%2F%2Fapps-portal.safe.global%2Ftx-builder`; From d11339db025d0635e6984a611c92b18f856a7c63 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 18 Feb 2025 03:17:18 +0000 Subject: [PATCH 09/10] fix: keeperBot handling of custom errors, use the getContracts() utility --- contracts/scripts/keeperBot.ts | 66 ++++++--------------- contracts/scripts/populatePolicyRegistry.ts | 8 +-- 2 files changed, 19 insertions(+), 55 deletions(-) diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index 59d9a92c6..bd4ebcf35 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -1,18 +1,9 @@ -import { - PNK, - RandomizerRNG, - BlockHashRNG, - SortitionModule, - KlerosCore, - KlerosCoreNeo, - SortitionModuleNeo, - DisputeKitClassic, - ChainlinkRNG, -} from "../typechain-types"; +import hre from "hardhat"; +import { toBigInt, BigNumberish, getNumber, BytesLike } from "ethers"; +import { SortitionModule, SortitionModuleNeo } from "../typechain-types"; import env from "./utils/env"; import loggerFactory from "./utils/logger"; -import { toBigInt, BigNumberish, getNumber } from "ethers"; -import hre = require("hardhat"); +import { Cores, getContracts as getContractsForCoreType } from "./utils/contracts"; let request: (url: string, query: string) => Promise; // Workaround graphql-request ESM import const { ethers } = hre; @@ -44,29 +35,12 @@ const loggerOptions = env.optionalNoDefault("LOGTAIL_TOKEN_KEEPER_BOT") const logger = loggerFactory.createLogger(loggerOptions); const getContracts = async () => { - let core: KlerosCore | KlerosCoreNeo; - let sortition: SortitionModule | SortitionModuleNeo; - let disputeKitClassic: DisputeKitClassic; const coreType = Cores[CORE_TYPE.toUpperCase() as keyof typeof Cores]; - switch (coreType) { - case Cores.NEO: - core = (await ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo; - sortition = (await ethers.getContract("SortitionModuleNeo")) as SortitionModuleNeo; - disputeKitClassic = (await ethers.getContract("DisputeKitClassicNeo")) as DisputeKitClassic; - break; - case Cores.BASE: - core = (await ethers.getContract("KlerosCore")) as KlerosCore; - sortition = (await ethers.getContract("SortitionModule")) as SortitionModule; - disputeKitClassic = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic; - break; - default: - throw new Error("Invalid core type, must be one of base, neo"); + if (coreType === Cores.UNIVERSITY) { + throw new Error("University is not supported yet"); } - const chainlinkRng = await ethers.getContractOrNull("ChainlinkRNG"); - const randomizerRng = await ethers.getContractOrNull("RandomizerRNG"); - const blockHashRNG = await ethers.getContractOrNull("BlockHashRNG"); - const pnk = (await ethers.getContract("PNK")) as PNK; - return { core, sortition, chainlinkRng, randomizerRng, blockHashRNG, disputeKitClassic, pnk }; + const contracts = await getContractsForCoreType(hre, coreType); + return { ...contracts, sortition: contracts.sortition as SortitionModule | SortitionModuleNeo }; }; type Contribution = { @@ -87,6 +61,7 @@ type Dispute = { }; type CustomError = { + data: BytesLike; reason: string; code: string; errorArgs: any[]; @@ -94,11 +69,6 @@ type CustomError = { errorSignature: string; }; -enum Cores { - BASE, - NEO, -} - enum Phase { STAKING = "staking", GENERATING = "generating", @@ -185,7 +155,7 @@ const handleError = (e: any) => { const isRngReady = async () => { const { chainlinkRng, randomizerRng, blockHashRNG, sortition } = await getContracts(); const currentRng = await sortition.rng(); - if (currentRng === chainlinkRng?.target) { + if (currentRng === chainlinkRng?.target && chainlinkRng !== null) { const requestID = await chainlinkRng.lastRequestId(); const n = await chainlinkRng.randomNumbers(requestID); if (Number(n) === 0) { @@ -195,7 +165,7 @@ const isRngReady = async () => { logger.info(`ChainlinkRNG is ready: ${n.toString()}`); return true; } - } else if (currentRng === randomizerRng?.target) { + } else if (currentRng === randomizerRng?.target && randomizerRng !== null) { const requestID = await randomizerRng.lastRequestId(); const n = await randomizerRng.randomNumbers(requestID); if (Number(n) === 0) { @@ -205,7 +175,7 @@ const isRngReady = async () => { logger.info(`RandomizerRNG is ready: ${n.toString()}`); return true; } - } else if (currentRng === blockHashRNG?.target) { + } else if (currentRng === blockHashRNG?.target && blockHashRNG !== null) { const requestBlock = await sortition.randomNumberRequestBlock(); const lookahead = await sortition.rngLookahead(); const n = await blockHashRNG.receiveRandomness.staticCall(requestBlock + lookahead); @@ -229,7 +199,8 @@ const passPhase = async () => { await sortition.passPhase.staticCall(); } catch (e) { const error = e as CustomError; - logger.info(`passPhase: not ready yet because of ${error?.reason ?? error?.errorName ?? error?.code}`); + const errorDescription = sortition.interface.parseError(error.data)?.signature; + logger.info(`passPhase: not ready yet because of ${errorDescription}`); return success; } const before = getNumber(await sortition.phase()); @@ -254,11 +225,8 @@ const passPeriod = async (dispute: { id: string }) => { await core.passPeriod.staticCall(dispute.id); } catch (e) { const error = e as CustomError; - logger.info( - `passPeriod: not ready yet for dispute ${dispute.id} because of error ${ - error?.reason ?? error?.errorName ?? error?.code - }` - ); + const errorDescription = core.interface.parseError(error.data)?.signature; + logger.info(`passPeriod: not ready yet for dispute ${dispute.id} because of error ${errorDescription}`); return success; } const before = (await core.disputes(dispute.id)).period; @@ -567,7 +535,7 @@ async function main() { } if (await isPhaseDrawing()) { let maxDrawingTimePassed = await hasMaxDrawingTimePassed(); - for (dispute of disputesWithoutJurors) { + for (const dispute of disputesWithoutJurors) { if (maxDrawingTimePassed) { logger.info("Max drawing time passed"); break; diff --git a/contracts/scripts/populatePolicyRegistry.ts b/contracts/scripts/populatePolicyRegistry.ts index dc94a5bd8..6ffdacd8e 100644 --- a/contracts/scripts/populatePolicyRegistry.ts +++ b/contracts/scripts/populatePolicyRegistry.ts @@ -36,7 +36,7 @@ task("populate:policy-registry", "Populates the policy registry for each court") types.int ) .setAction(async (taskArgs, hre) => { - const { deployments, getNamedAccounts, getChainId, ethers, network } = hre; + const { getNamedAccounts, getChainId, ethers, network } = hre; // fallback to hardhat node signers on local network const deployer = (await getNamedAccounts()).deployer ?? (await ethers.getSigners())[0].address; @@ -102,11 +102,7 @@ task("populate:policy-registry", "Populates the policy registry for each court") 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( - "PolicyRegistry", - policyRegistryDeployment.address - )) as PolicyRegistry; + const policyRegistry = await ethers.getContract("PolicyRegistry"); for await (const policy of policiesV2) { console.log("Populating policy for %s Court (%d): %s", policy.name, policy.court, policy.uri); From b1962653154c8068123872a10db69f99d1131f13 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 26 Feb 2025 12:36:50 +0000 Subject: [PATCH 10/10] chore: evidence period reduced for courtID 29 --- contracts/config/courts.v2.mainnet-neo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/config/courts.v2.mainnet-neo.json b/contracts/config/courts.v2.mainnet-neo.json index 0c5fa82f6..db7ec6b98 100644 --- a/contracts/config/courts.v2.mainnet-neo.json +++ b/contracts/config/courts.v2.mainnet-neo.json @@ -457,7 +457,7 @@ "alpha": "5000", "jurorsForCourtJump": "15", "timesPerPeriod": [ - 108000, + 21600, 216000, 216000, 216000