Skip to content

Commit f816ca3

Browse files
authored
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-frontend-rendering
2 parents b3817bd + 99f9852 commit f816ca3

File tree

10 files changed

+241
-90
lines changed

10 files changed

+241
-90
lines changed

contracts/deploy/upgrade-all.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const deployUpgradeAll: DeployFunction = async (hre: HardhatRuntimeEnvironment)
8181
await upgrade(disputeKitClassic, "initialize6", []);
8282
await upgrade(disputeTemplateRegistry, "initialize2", []);
8383
await upgrade(evidence, "initialize2", []);
84-
await upgrade(core, "initialize4", []);
84+
await upgrade(core, "initialize5", []);
8585
await upgrade(policyRegistry, "initialize2", []);
8686
await upgrade(sortition, "initialize3", []);
8787
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#! /usr/bin/env bash
2+
3+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
4+
5+
declare -A rpcUrls
6+
rpcUrls["arbitrum"]=$(mesc url arbitrum_alchemy)
7+
rpcUrls["arbitrumSepolia"]=$(mesc url arbitrumSepolia_alchemy)
8+
rpcUrls["arbitrumSepoliaDevnet"]=$(mesc url arbitrumSepolia_alchemy)
9+
10+
for c in arbitrum arbitrumSepolia arbitrumSepoliaDevnet; do
11+
echo "$c"
12+
for f in "$SCRIPT_DIR"/../deployments/"$c"/*_Proxy.json; do
13+
address=$(jq -r .address "$f")
14+
block=$(jq -r .receipt.blockNumber "$f")
15+
basename "$f"
16+
results=$(cast logs --from-block "$block" --to-block latest 0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2 --address "$address" --rpc-url "${rpcUrls[$c]}" --json | jq -r .[].data)
17+
for result in $results; do
18+
cast --to-dec "$result"
19+
done
20+
echo
21+
done
22+
echo "--------------------------------"
23+
done

contracts/scripts/keeperBot.ts

Lines changed: 136 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
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";
11+
const MAX_DRAW_CALLS_WITHOUT_JURORS = 10;
1012
const MAX_DRAW_ITERATIONS = 30;
1113
const MAX_EXECUTE_ITERATIONS = 20;
1214
const MAX_DELAYED_STAKES_ITERATIONS = 50;
@@ -76,73 +78,115 @@ enum Phase {
7678
}
7779
const PHASES = Object.values(Phase);
7880

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+
79108
const getNonFinalDisputes = async (): Promise<Dispute[]> => {
80-
const nonFinalDisputesRequest = `{
81-
disputes(where: {period_not: execution}) {
82-
period
83-
id
84-
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+
}
85117
}
86-
}`;
118+
`;
87119
// TODO: use a local graph node if chainId is HARDHAT
88-
const result = await request(SUBGRAPH_URL, nonFinalDisputesRequest);
89-
const { disputes } = result as { disputes: Dispute[] };
120+
type Disputes = { disputes: Dispute[] };
121+
const { disputes } = await request<Disputes>(SUBGRAPH_URL, query);
90122
return disputes;
91123
};
92124

93125
const getAppealContributions = async (disputeId: string): Promise<Contribution[]> => {
94-
const appealContributionsRequest = (disputeId: string) => `{
95-
contributions(where: {coreDispute: "${disputeId}"}) {
96-
contributor {
97-
id
98-
}
99-
... on ClassicContribution {
100-
choice
101-
rewardWithdrawn
102-
}
103-
coreDispute {
104-
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+
}
105140
}
106141
}
107-
}`;
142+
`;
143+
const variables = { disputeId };
144+
type AppealContributions = { contributions: Contribution[] };
108145
// TODO: use a local graph node if chainId is HARDHAT
109-
const result = await request(SUBGRAPH_URL, appealContributionsRequest(disputeId));
110-
const { contributions } = result as { contributions: Contribution[] };
146+
const { contributions } = await request<AppealContributions>(SUBGRAPH_URL, query, variables);
111147
return contributions;
112148
};
113149

114150
const getDisputesWithUnexecutedRuling = async (): Promise<Dispute[]> => {
115-
const disputesWithUnexecutedRuling = `{
116-
disputes(where: {period: execution, ruled: false}) {
117-
id
118-
currentRoundIndex
119-
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+
}
120159
}
121-
}`;
160+
`;
122161
// TODO: use a local graph node if chainId is HARDHAT
123-
const result = (await request(SUBGRAPH_URL, disputesWithUnexecutedRuling)) as { disputes: Dispute[] };
124-
return result.disputes;
162+
type Disputes = { disputes: Dispute[] };
163+
const { disputes } = await request<Disputes>(SUBGRAPH_URL, query);
164+
return disputes;
125165
};
126166

127167
const getUniqueDisputes = (disputes: Dispute[]): Dispute[] => {
128168
return [...new Map(disputes.map((v) => [v.id, v])).values()];
129169
};
130170

131171
const getDisputesWithContributionsNotYetWithdrawn = async (): Promise<Dispute[]> => {
132-
const disputesWithContributionsNotYetWithdrawn = `{
133-
classicContributions(where: {rewardWithdrawn: false}) {
134-
coreDispute {
135-
id
136-
period
137-
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+
}
138181
}
139182
}
140-
}`;
183+
`;
141184
// TODO: use a local graph node if chainId is HARDHAT
142-
const result = (await request(SUBGRAPH_URL, disputesWithContributionsNotYetWithdrawn)) as {
185+
type Contributions = {
143186
classicContributions: { coreDispute: Dispute }[];
144187
};
145-
const disputes = result.classicContributions
188+
const { classicContributions } = await request<Contributions>(SUBGRAPH_URL, query);
189+
const disputes = classicContributions
146190
.filter((contribution) => contribution.coreDispute.period === "execution")
147191
.map((dispute) => dispute.coreDispute);
148192
return getUniqueDisputes(disputes);
@@ -248,7 +292,19 @@ const drawJurors = async (dispute: { id: string; currentRoundIndex: string }, it
248292
const { core } = await getContracts();
249293
let success = false;
250294
try {
251-
await core.draw.staticCall(dispute.id, iterations, HIGH_GAS_LIMIT);
295+
const simulatedIterations = iterations * MAX_DRAW_CALLS_WITHOUT_JURORS; // Drawing will be skipped as long as no juror is available in the next MAX_DRAW_CALLS_WITHOUT_JURORS calls to draw() given this nb of iterations.
296+
const { drawnJurors: drawnJurorsBefore } = await core.getRoundInfo(dispute.id, dispute.currentRoundIndex);
297+
const nbDrawnJurors = (await core.draw.staticCall(dispute.id, simulatedIterations, HIGH_GAS_LIMIT)) as bigint;
298+
const extraJurors = nbDrawnJurors - BigInt(drawnJurorsBefore.length);
299+
logger.debug(
300+
`Draw: ${extraJurors} jurors available in the next ${simulatedIterations} iterations for dispute ${dispute.id}`
301+
);
302+
if (extraJurors <= 0n) {
303+
logger.warn(
304+
`Draw: skipping, no jurors available in the next ${simulatedIterations} iterations for dispute ${dispute.id}`
305+
);
306+
return success;
307+
}
252308
} catch (e) {
253309
logger.error(`Draw: will fail for ${dispute.id}, skipping`);
254310
return success;
@@ -306,49 +362,55 @@ const executeRuling = async (dispute: { id: string }) => {
306362
};
307363

308364
const withdrawAppealContribution = async (
309-
disputeId: string,
310-
roundId: string,
365+
coreDisputeId: string,
366+
coreRoundId: string,
311367
contribution: Contribution
312368
): Promise<boolean> => {
313-
const { disputeKitClassic: kit } = await getContracts();
369+
const { disputeKit, localDisputeId, localRoundId } = await getDisputeKit(coreDisputeId, coreRoundId);
314370
let success = false;
315371
let amountWithdrawn = 0n;
316372
try {
317-
amountWithdrawn = await kit.withdrawFeesAndRewards.staticCall(
318-
disputeId,
373+
amountWithdrawn = await disputeKit.withdrawFeesAndRewards.staticCall(
374+
localDisputeId,
319375
contribution.contributor.id,
320-
roundId,
376+
localRoundId,
321377
contribution.choice
322378
);
323379
} catch (e) {
324380
logger.warn(
325-
`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`
326382
);
327383
return success;
328384
}
329385
if (amountWithdrawn === 0n) {
330386
logger.debug(
331-
`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`
332388
);
333389
return success;
334390
}
335391
try {
336392
logger.info(
337-
`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}`
338394
);
339395
const gas =
340-
((await kit.withdrawFeesAndRewards.estimateGas(
341-
disputeId,
396+
((await disputeKit.withdrawFeesAndRewards.estimateGas(
397+
localDisputeId,
342398
contribution.contributor.id,
343-
roundId,
399+
localRoundId,
344400
contribution.choice
345401
)) *
346402
150n) /
347403
100n; // 50% extra gas
348404
const tx = await (
349-
await kit.withdrawFeesAndRewards(disputeId, contribution.contributor.id, roundId, contribution.choice, {
350-
gasLimit: gas,
351-
})
405+
await disputeKit.withdrawFeesAndRewards(
406+
localDisputeId,
407+
contribution.contributor.id,
408+
localRoundId,
409+
contribution.choice,
410+
{
411+
gasLimit: gas,
412+
}
413+
)
352414
).wait();
353415
logger.info(`WithdrawFeesAndRewards txID: ${tx?.hash}`);
354416
success = true;
@@ -445,10 +507,13 @@ const sendHeartbeat = async () => {
445507
}
446508
};
447509

510+
const shutdown = async () => {
511+
logger.info("Shutting down");
512+
await delay(2000); // Some log messages may be lost otherwise
513+
};
514+
448515
async function main() {
449-
const graphqlRequest = await import("graphql-request"); // Workaround graphql-request ESM import
450-
request = graphqlRequest.request;
451-
const { core, sortition, disputeKitClassic } = await getContracts();
516+
const { core, sortition, disputeKitShutter } = await getContracts();
452517

453518
const getBlockTime = async () => {
454519
return await ethers.provider.getBlock("latest").then((block) => {
@@ -489,6 +554,14 @@ async function main() {
489554

490555
await sendHeartbeat();
491556

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

494567
// Retrieve the disputes which are in a non-final period
@@ -609,7 +682,8 @@ async function main() {
609682
// ----------------------------------------------- //
610683
// REPARTITIONS EXECUTION //
611684
// ----------------------------------------------- //
612-
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);
613687
let numberOfMissingRepartitions = await getNumberOfMissingRepartitions(dispute, coherentCount);
614688
do {
615689
const executeIterations = Math.min(MAX_EXECUTE_ITERATIONS, numberOfMissingRepartitions);
@@ -673,8 +747,7 @@ async function main() {
673747

674748
await sendHeartbeat();
675749

676-
logger.info("Shutting down");
677-
await delay(2000); // Some log messages may be lost otherwise
750+
await shutdown();
678751
}
679752

680753
main()

0 commit comments

Comments
 (0)