|
1 | 1 | import hre from "hardhat";
|
2 | 2 | import { toBigInt, BigNumberish, getNumber, BytesLike } from "ethers";
|
3 |
| -import { SortitionModule, SortitionModuleNeo } from "../typechain-types"; |
| 3 | +import { DisputeKitClassic, DisputeKitShutter, SortitionModule, SortitionModuleNeo } from "../typechain-types"; |
4 | 4 | import env from "./utils/env";
|
5 | 5 | import loggerFactory from "./utils/logger";
|
6 | 6 | import { Cores, getContracts as getContractsForCoreType } from "./utils/contracts";
|
| 7 | +import { shutterAutoReveal } from "./keeperBotShutter"; |
7 | 8 |
|
8 |
| -let request: <T>(url: string, query: string) => Promise<T>; // Workaround graphql-request ESM import |
9 | 9 | const { ethers } = hre;
|
| 10 | +const SHUTTER_AUTO_REVEAL_ONLY = env.optional("SHUTTER_AUTO_REVEAL_ONLY", "false") === "true"; |
10 | 11 | const MAX_DRAW_CALLS_WITHOUT_JURORS = 10;
|
11 | 12 | const MAX_DRAW_ITERATIONS = 30;
|
12 | 13 | const MAX_EXECUTE_ITERATIONS = 20;
|
@@ -77,73 +78,115 @@ enum Phase {
|
77 | 78 | }
|
78 | 79 | const PHASES = Object.values(Phase);
|
79 | 80 |
|
| 81 | +const getDisputeKit = async ( |
| 82 | + coreDisputeId: string, |
| 83 | + coreRoundId: string |
| 84 | +): Promise<{ |
| 85 | + disputeKit: DisputeKitClassic | DisputeKitShutter; |
| 86 | + localDisputeId: bigint; |
| 87 | + localRoundId: bigint; |
| 88 | +}> => { |
| 89 | + const { core, disputeKitClassic, disputeKitShutter } = await getContracts(); |
| 90 | + const round = await core.getRoundInfo(coreDisputeId, coreRoundId); |
| 91 | + const disputeKitAddress = await core.disputeKits(round.disputeKitID); |
| 92 | + let disputeKit: DisputeKitClassic | DisputeKitShutter; |
| 93 | + switch (disputeKitAddress) { |
| 94 | + case disputeKitClassic.target: |
| 95 | + disputeKit = disputeKitClassic; |
| 96 | + break; |
| 97 | + case disputeKitShutter?.target: |
| 98 | + if (!disputeKitShutter) throw new Error(`DisputeKitShutter not deployed`); |
| 99 | + disputeKit = disputeKitShutter; |
| 100 | + break; |
| 101 | + default: |
| 102 | + throw new Error(`Unknown dispute kit: ${disputeKitAddress}`); |
| 103 | + } |
| 104 | + const [localDisputeId, localRoundId] = await disputeKit.getLocalDisputeRoundID(coreDisputeId, coreRoundId); |
| 105 | + return { disputeKit, localDisputeId, localRoundId }; |
| 106 | +}; |
| 107 | + |
80 | 108 | const getNonFinalDisputes = async (): Promise<Dispute[]> => {
|
81 |
| - const nonFinalDisputesRequest = `{ |
82 |
| - disputes(where: {period_not: execution}) { |
83 |
| - period |
84 |
| - id |
85 |
| - currentRoundIndex |
| 109 | + const { gql, request } = await import("graphql-request"); // workaround for ESM import |
| 110 | + const query = gql` |
| 111 | + query NonFinalDisputes { |
| 112 | + disputes(where: { period_not: execution }) { |
| 113 | + period |
| 114 | + id |
| 115 | + currentRoundIndex |
| 116 | + } |
86 | 117 | }
|
87 |
| - }`; |
| 118 | + `; |
88 | 119 | // TODO: use a local graph node if chainId is HARDHAT
|
89 |
| - const result = await request(SUBGRAPH_URL, nonFinalDisputesRequest); |
90 |
| - const { disputes } = result as { disputes: Dispute[] }; |
| 120 | + type Disputes = { disputes: Dispute[] }; |
| 121 | + const { disputes } = await request<Disputes>(SUBGRAPH_URL, query); |
91 | 122 | return disputes;
|
92 | 123 | };
|
93 | 124 |
|
94 | 125 | const getAppealContributions = async (disputeId: string): Promise<Contribution[]> => {
|
95 |
| - const appealContributionsRequest = (disputeId: string) => `{ |
96 |
| - contributions(where: {coreDispute: "${disputeId}"}) { |
97 |
| - contributor { |
98 |
| - id |
99 |
| - } |
100 |
| - ... on ClassicContribution { |
101 |
| - choice |
102 |
| - rewardWithdrawn |
103 |
| - } |
104 |
| - coreDispute { |
105 |
| - currentRoundIndex |
| 126 | + const { gql, request } = await import("graphql-request"); // workaround for ESM import |
| 127 | + const query = gql` |
| 128 | + query AppealContributions($disputeId: String!) { |
| 129 | + contributions(where: { coreDispute: $disputeId }) { |
| 130 | + contributor { |
| 131 | + id |
| 132 | + } |
| 133 | + ... on ClassicContribution { |
| 134 | + choice |
| 135 | + rewardWithdrawn |
| 136 | + } |
| 137 | + coreDispute { |
| 138 | + currentRoundIndex |
| 139 | + } |
106 | 140 | }
|
107 | 141 | }
|
108 |
| - }`; |
| 142 | + `; |
| 143 | + const variables = { disputeId }; |
| 144 | + type AppealContributions = { contributions: Contribution[] }; |
109 | 145 | // TODO: use a local graph node if chainId is HARDHAT
|
110 |
| - const result = await request(SUBGRAPH_URL, appealContributionsRequest(disputeId)); |
111 |
| - const { contributions } = result as { contributions: Contribution[] }; |
| 146 | + const { contributions } = await request<AppealContributions>(SUBGRAPH_URL, query, variables); |
112 | 147 | return contributions;
|
113 | 148 | };
|
114 | 149 |
|
115 | 150 | const getDisputesWithUnexecutedRuling = async (): Promise<Dispute[]> => {
|
116 |
| - const disputesWithUnexecutedRuling = `{ |
117 |
| - disputes(where: {period: execution, ruled: false}) { |
118 |
| - id |
119 |
| - currentRoundIndex |
120 |
| - period |
| 151 | + const { gql, request } = await import("graphql-request"); // workaround for ESM import |
| 152 | + const query = gql` |
| 153 | + query DisputesWithUnexecutedRuling { |
| 154 | + disputes(where: { period: execution, ruled: false }) { |
| 155 | + id |
| 156 | + currentRoundIndex |
| 157 | + period |
| 158 | + } |
121 | 159 | }
|
122 |
| - }`; |
| 160 | + `; |
123 | 161 | // TODO: use a local graph node if chainId is HARDHAT
|
124 |
| - const result = (await request(SUBGRAPH_URL, disputesWithUnexecutedRuling)) as { disputes: Dispute[] }; |
125 |
| - return result.disputes; |
| 162 | + type Disputes = { disputes: Dispute[] }; |
| 163 | + const { disputes } = await request<Disputes>(SUBGRAPH_URL, query); |
| 164 | + return disputes; |
126 | 165 | };
|
127 | 166 |
|
128 | 167 | const getUniqueDisputes = (disputes: Dispute[]): Dispute[] => {
|
129 | 168 | return [...new Map(disputes.map((v) => [v.id, v])).values()];
|
130 | 169 | };
|
131 | 170 |
|
132 | 171 | const getDisputesWithContributionsNotYetWithdrawn = async (): Promise<Dispute[]> => {
|
133 |
| - const disputesWithContributionsNotYetWithdrawn = `{ |
134 |
| - classicContributions(where: {rewardWithdrawn: false}) { |
135 |
| - coreDispute { |
136 |
| - id |
137 |
| - period |
138 |
| - currentRoundIndex |
| 172 | + const { gql, request } = await import("graphql-request"); // workaround for ESM import |
| 173 | + const query = gql` |
| 174 | + query DisputesWithContributionsNotYetWithdrawn { |
| 175 | + classicContributions(where: { rewardWithdrawn: false }) { |
| 176 | + coreDispute { |
| 177 | + id |
| 178 | + period |
| 179 | + currentRoundIndex |
| 180 | + } |
139 | 181 | }
|
140 | 182 | }
|
141 |
| - }`; |
| 183 | + `; |
142 | 184 | // TODO: use a local graph node if chainId is HARDHAT
|
143 |
| - const result = (await request(SUBGRAPH_URL, disputesWithContributionsNotYetWithdrawn)) as { |
| 185 | + type Contributions = { |
144 | 186 | classicContributions: { coreDispute: Dispute }[];
|
145 | 187 | };
|
146 |
| - const disputes = result.classicContributions |
| 188 | + const { classicContributions } = await request<Contributions>(SUBGRAPH_URL, query); |
| 189 | + const disputes = classicContributions |
147 | 190 | .filter((contribution) => contribution.coreDispute.period === "execution")
|
148 | 191 | .map((dispute) => dispute.coreDispute);
|
149 | 192 | return getUniqueDisputes(disputes);
|
@@ -319,49 +362,55 @@ const executeRuling = async (dispute: { id: string }) => {
|
319 | 362 | };
|
320 | 363 |
|
321 | 364 | const withdrawAppealContribution = async (
|
322 |
| - disputeId: string, |
323 |
| - roundId: string, |
| 365 | + coreDisputeId: string, |
| 366 | + coreRoundId: string, |
324 | 367 | contribution: Contribution
|
325 | 368 | ): Promise<boolean> => {
|
326 |
| - const { disputeKitClassic: kit } = await getContracts(); |
| 369 | + const { disputeKit, localDisputeId, localRoundId } = await getDisputeKit(coreDisputeId, coreRoundId); |
327 | 370 | let success = false;
|
328 | 371 | let amountWithdrawn = 0n;
|
329 | 372 | try {
|
330 |
| - amountWithdrawn = await kit.withdrawFeesAndRewards.staticCall( |
331 |
| - disputeId, |
| 373 | + amountWithdrawn = await disputeKit.withdrawFeesAndRewards.staticCall( |
| 374 | + localDisputeId, |
332 | 375 | contribution.contributor.id,
|
333 |
| - roundId, |
| 376 | + localRoundId, |
334 | 377 | contribution.choice
|
335 | 378 | );
|
336 | 379 | } catch (e) {
|
337 | 380 | logger.warn(
|
338 |
| - `WithdrawFeesAndRewards: will fail for dispute #${disputeId}, round #${roundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}, skipping` |
| 381 | + `WithdrawFeesAndRewards: will fail for core dispute #${coreDisputeId}, round #${coreRoundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}, skipping` |
339 | 382 | );
|
340 | 383 | return success;
|
341 | 384 | }
|
342 | 385 | if (amountWithdrawn === 0n) {
|
343 | 386 | logger.debug(
|
344 |
| - `WithdrawFeesAndRewards: no fees or rewards to withdraw for dispute #${disputeId}, round #${roundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}, skipping` |
| 387 | + `WithdrawFeesAndRewards: no fees or rewards to withdraw for core dispute #${coreDisputeId}, round #${coreRoundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}, skipping` |
345 | 388 | );
|
346 | 389 | return success;
|
347 | 390 | }
|
348 | 391 | try {
|
349 | 392 | logger.info(
|
350 |
| - `WithdrawFeesAndRewards: appeal contribution for dispute #${disputeId}, round #${roundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}` |
| 393 | + `WithdrawFeesAndRewards: appeal contribution for core dispute #${coreDisputeId}, round #${coreRoundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}` |
351 | 394 | );
|
352 | 395 | const gas =
|
353 |
| - ((await kit.withdrawFeesAndRewards.estimateGas( |
354 |
| - disputeId, |
| 396 | + ((await disputeKit.withdrawFeesAndRewards.estimateGas( |
| 397 | + localDisputeId, |
355 | 398 | contribution.contributor.id,
|
356 |
| - roundId, |
| 399 | + localRoundId, |
357 | 400 | contribution.choice
|
358 | 401 | )) *
|
359 | 402 | 150n) /
|
360 | 403 | 100n; // 50% extra gas
|
361 | 404 | const tx = await (
|
362 |
| - await kit.withdrawFeesAndRewards(disputeId, contribution.contributor.id, roundId, contribution.choice, { |
363 |
| - gasLimit: gas, |
364 |
| - }) |
| 405 | + await disputeKit.withdrawFeesAndRewards( |
| 406 | + localDisputeId, |
| 407 | + contribution.contributor.id, |
| 408 | + localRoundId, |
| 409 | + contribution.choice, |
| 410 | + { |
| 411 | + gasLimit: gas, |
| 412 | + } |
| 413 | + ) |
365 | 414 | ).wait();
|
366 | 415 | logger.info(`WithdrawFeesAndRewards txID: ${tx?.hash}`);
|
367 | 416 | success = true;
|
@@ -458,10 +507,13 @@ const sendHeartbeat = async () => {
|
458 | 507 | }
|
459 | 508 | };
|
460 | 509 |
|
| 510 | +const shutdown = async () => { |
| 511 | + logger.info("Shutting down"); |
| 512 | + await delay(2000); // Some log messages may be lost otherwise |
| 513 | +}; |
| 514 | + |
461 | 515 | async function main() {
|
462 |
| - const graphqlRequest = await import("graphql-request"); // Workaround graphql-request ESM import |
463 |
| - request = graphqlRequest.request; |
464 |
| - const { core, sortition, disputeKitClassic } = await getContracts(); |
| 516 | + const { core, sortition, disputeKitShutter } = await getContracts(); |
465 | 517 |
|
466 | 518 | const getBlockTime = async () => {
|
467 | 519 | return await ethers.provider.getBlock("latest").then((block) => {
|
@@ -502,6 +554,14 @@ async function main() {
|
502 | 554 |
|
503 | 555 | await sendHeartbeat();
|
504 | 556 |
|
| 557 | + logger.info("Auto-revealing disputes"); |
| 558 | + await shutterAutoReveal(disputeKitShutter, DISPUTES_TO_SKIP); |
| 559 | + if (SHUTTER_AUTO_REVEAL_ONLY) { |
| 560 | + logger.debug("Shutter auto-reveal only, skipping other actions"); |
| 561 | + await shutdown(); |
| 562 | + return; |
| 563 | + } |
| 564 | + |
505 | 565 | logger.info(`Current phase: ${PHASES[getNumber(await sortition.phase())]}`);
|
506 | 566 |
|
507 | 567 | // Retrieve the disputes which are in a non-final period
|
@@ -622,7 +682,8 @@ async function main() {
|
622 | 682 | // ----------------------------------------------- //
|
623 | 683 | // REPARTITIONS EXECUTION //
|
624 | 684 | // ----------------------------------------------- //
|
625 |
| - const coherentCount = await disputeKitClassic.getCoherentCount(dispute.id, dispute.currentRoundIndex); |
| 685 | + const { disputeKit } = await getDisputeKit(dispute.id, dispute.currentRoundIndex); |
| 686 | + const coherentCount = await disputeKit.getCoherentCount(dispute.id, dispute.currentRoundIndex); |
626 | 687 | let numberOfMissingRepartitions = await getNumberOfMissingRepartitions(dispute, coherentCount);
|
627 | 688 | do {
|
628 | 689 | const executeIterations = Math.min(MAX_EXECUTE_ITERATIONS, numberOfMissingRepartitions);
|
@@ -686,8 +747,7 @@ async function main() {
|
686 | 747 |
|
687 | 748 | await sendHeartbeat();
|
688 | 749 |
|
689 |
| - logger.info("Shutting down"); |
690 |
| - await delay(2000); // Some log messages may be lost otherwise |
| 750 | + await shutdown(); |
691 | 751 | }
|
692 | 752 |
|
693 | 753 | main()
|
|
0 commit comments