|
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