Skip to content

Commit 99f9852

Browse files
committed
feat: shutter auto-reveal logic integrated into the main bot, improved GQL support
1 parent e52d59b commit 99f9852

File tree

2 files changed

+170
-78
lines changed

2 files changed

+170
-78
lines changed

contracts/scripts/keeperBot.ts

Lines changed: 122 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import hre from "hardhat";
22
import { toBigInt, BigNumberish, getNumber, BytesLike } from "ethers";
3-
import { SortitionModule, SortitionModuleNeo } from "../typechain-types";
3+
import { DisputeKitClassic, DisputeKitShutter, SortitionModule, SortitionModuleNeo } from "../typechain-types";
44
import env from "./utils/env";
55
import loggerFactory from "./utils/logger";
66
import { Cores, getContracts as getContractsForCoreType } from "./utils/contracts";
7+
import { shutterAutoReveal } from "./keeperBotShutter";
78

8-
let request: <T>(url: string, query: string) => Promise<T>; // Workaround graphql-request ESM import
99
const { ethers } = hre;
10+
const SHUTTER_AUTO_REVEAL_ONLY = env.optional("SHUTTER_AUTO_REVEAL_ONLY", "false") === "true";
1011
const MAX_DRAW_CALLS_WITHOUT_JURORS = 10;
1112
const MAX_DRAW_ITERATIONS = 30;
1213
const MAX_EXECUTE_ITERATIONS = 20;
@@ -77,73 +78,115 @@ enum Phase {
7778
}
7879
const PHASES = Object.values(Phase);
7980

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+
80108
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+
}
86117
}
87-
}`;
118+
`;
88119
// 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);
91122
return disputes;
92123
};
93124

94125
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+
}
106140
}
107141
}
108-
}`;
142+
`;
143+
const variables = { disputeId };
144+
type AppealContributions = { contributions: Contribution[] };
109145
// 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);
112147
return contributions;
113148
};
114149

115150
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+
}
121159
}
122-
}`;
160+
`;
123161
// 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;
126165
};
127166

128167
const getUniqueDisputes = (disputes: Dispute[]): Dispute[] => {
129168
return [...new Map(disputes.map((v) => [v.id, v])).values()];
130169
};
131170

132171
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+
}
139181
}
140182
}
141-
}`;
183+
`;
142184
// TODO: use a local graph node if chainId is HARDHAT
143-
const result = (await request(SUBGRAPH_URL, disputesWithContributionsNotYetWithdrawn)) as {
185+
type Contributions = {
144186
classicContributions: { coreDispute: Dispute }[];
145187
};
146-
const disputes = result.classicContributions
188+
const { classicContributions } = await request<Contributions>(SUBGRAPH_URL, query);
189+
const disputes = classicContributions
147190
.filter((contribution) => contribution.coreDispute.period === "execution")
148191
.map((dispute) => dispute.coreDispute);
149192
return getUniqueDisputes(disputes);
@@ -319,49 +362,55 @@ const executeRuling = async (dispute: { id: string }) => {
319362
};
320363

321364
const withdrawAppealContribution = async (
322-
disputeId: string,
323-
roundId: string,
365+
coreDisputeId: string,
366+
coreRoundId: string,
324367
contribution: Contribution
325368
): Promise<boolean> => {
326-
const { disputeKitClassic: kit } = await getContracts();
369+
const { disputeKit, localDisputeId, localRoundId } = await getDisputeKit(coreDisputeId, coreRoundId);
327370
let success = false;
328371
let amountWithdrawn = 0n;
329372
try {
330-
amountWithdrawn = await kit.withdrawFeesAndRewards.staticCall(
331-
disputeId,
373+
amountWithdrawn = await disputeKit.withdrawFeesAndRewards.staticCall(
374+
localDisputeId,
332375
contribution.contributor.id,
333-
roundId,
376+
localRoundId,
334377
contribution.choice
335378
);
336379
} catch (e) {
337380
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`
339382
);
340383
return success;
341384
}
342385
if (amountWithdrawn === 0n) {
343386
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`
345388
);
346389
return success;
347390
}
348391
try {
349392
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}`
351394
);
352395
const gas =
353-
((await kit.withdrawFeesAndRewards.estimateGas(
354-
disputeId,
396+
((await disputeKit.withdrawFeesAndRewards.estimateGas(
397+
localDisputeId,
355398
contribution.contributor.id,
356-
roundId,
399+
localRoundId,
357400
contribution.choice
358401
)) *
359402
150n) /
360403
100n; // 50% extra gas
361404
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+
)
365414
).wait();
366415
logger.info(`WithdrawFeesAndRewards txID: ${tx?.hash}`);
367416
success = true;
@@ -458,10 +507,13 @@ const sendHeartbeat = async () => {
458507
}
459508
};
460509

510+
const shutdown = async () => {
511+
logger.info("Shutting down");
512+
await delay(2000); // Some log messages may be lost otherwise
513+
};
514+
461515
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();
465517

466518
const getBlockTime = async () => {
467519
return await ethers.provider.getBlock("latest").then((block) => {
@@ -502,6 +554,14 @@ async function main() {
502554

503555
await sendHeartbeat();
504556

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+
505565
logger.info(`Current phase: ${PHASES[getNumber(await sortition.phase())]}`);
506566

507567
// Retrieve the disputes which are in a non-final period
@@ -622,7 +682,8 @@ async function main() {
622682
// ----------------------------------------------- //
623683
// REPARTITIONS EXECUTION //
624684
// ----------------------------------------------- //
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);
626687
let numberOfMissingRepartitions = await getNumberOfMissingRepartitions(dispute, coherentCount);
627688
do {
628689
const executeIterations = Math.min(MAX_EXECUTE_ITERATIONS, numberOfMissingRepartitions);
@@ -686,8 +747,7 @@ async function main() {
686747

687748
await sendHeartbeat();
688749

689-
logger.info("Shutting down");
690-
await delay(2000); // Some log messages may be lost otherwise
750+
await shutdown();
691751
}
692752

693753
main()

0 commit comments

Comments
 (0)