Skip to content

Commit 8cb7245

Browse files
committed
test: reproduction of the staking and locked stakes bug
1 parent 6b6eaf4 commit 8cb7245

File tree

2 files changed

+206
-28
lines changed

2 files changed

+206
-28
lines changed

contracts/test/arbitration/draw.ts

Lines changed: 205 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
22
import { deployments, ethers, getNamedAccounts, network } from "hardhat";
3-
import { BigNumber, ContractTransaction, Wallet } from "ethers";
3+
import { BigNumber, ContractReceipt, ContractTransaction, Wallet } from "ethers";
44
import {
55
PNK,
66
KlerosCore,
77
ArbitrableExample,
88
HomeGateway,
99
DisputeKitClassic,
10-
RandomizerRNG,
11-
RandomizerMock,
1210
SortitionModule,
1311
} from "../../typechain-types";
1412
import { expect } from "chai";
13+
import { DrawEvent } from "../../typechain-types/src/kleros-v1/kleros-liquid-xdai/XKlerosLiquidV2";
1514

1615
/* eslint-disable no-unused-vars */
1716
/* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482
@@ -49,7 +48,11 @@ describe("Draw Benchmark", async () => {
4948
let homeGateway;
5049
let sortitionModule;
5150
let rng;
52-
let randomizer;
51+
let parentCourtMinStake: BigNumber;
52+
let childCourtMinStake: BigNumber;
53+
const RANDOM = BigNumber.from("61688911660239508166491237672720926005752254046266901728404745669596507231249");
54+
const PARENT_COURT = 1;
55+
const CHILD_COURT = 2;
5356

5457
beforeEach("Setup", async () => {
5558
({ deployer, relayer } = await getNamedAccounts());
@@ -63,11 +66,25 @@ describe("Draw Benchmark", async () => {
6366
core = (await ethers.getContract("KlerosCore")) as KlerosCore;
6467
homeGateway = (await ethers.getContract("HomeGatewayToEthereum")) as HomeGateway;
6568
arbitrable = (await ethers.getContract("ArbitrableExample")) as ArbitrableExample;
66-
rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
67-
randomizer = (await ethers.getContract("RandomizerMock")) as RandomizerMock;
6869
sortitionModule = (await ethers.getContract("SortitionModule")) as SortitionModule;
6970

70-
// CourtId 2
71+
parentCourtMinStake = await core
72+
.GENERAL_COURT()
73+
.then((courtId) => core.courts(courtId))
74+
.then((court) => court.minStake);
75+
76+
childCourtMinStake = BigNumber.from(10).pow(20).mul(3); // 300 PNK
77+
78+
// Make the tests more deterministic with this dummy RNG
79+
rng = await deployments.deploy("IncrementalNG", {
80+
from: deployer,
81+
args: [RANDOM],
82+
log: true,
83+
});
84+
85+
await sortitionModule.changeRandomNumberGenerator(rng.address, 20);
86+
87+
// CourtId 2 = CHILD_COURT
7188
const minStake = BigNumber.from(10).pow(20).mul(3); // 300 PNK
7289
const alpha = 10000;
7390
const feeForJuror = BigNumber.from(10).pow(17);
@@ -84,16 +101,24 @@ describe("Draw Benchmark", async () => {
84101
);
85102
});
86103

104+
type CountedDraws = { [address: string]: number };
87105
type SetStake = (wallet: Wallet) => Promise<void>;
88106
type ExpectFromDraw = (drawTx: Promise<ContractTransaction>) => Promise<void>;
89107

90-
const draw = async (setStake: SetStake, createDisputeCourtId: string, expectFromDraw: ExpectFromDraw) => {
108+
const draw = async (
109+
stake: SetStake,
110+
createDisputeCourtId: number,
111+
expectFromDraw: ExpectFromDraw,
112+
unstake: SetStake
113+
) => {
91114
const arbitrationCost = ONE_TENTH_ETH.mul(3);
92115
const [bridger] = await ethers.getSigners();
116+
const wallets: Wallet[] = [];
93117

94118
// Stake some jurors
95119
for (let i = 0; i < 16; i++) {
96120
const wallet = ethers.Wallet.createRandom().connect(ethers.provider);
121+
wallets.push(wallet);
97122

98123
await bridger.sendTransaction({
99124
to: wallet.address,
@@ -106,7 +131,7 @@ describe("Draw Benchmark", async () => {
106131

107132
await pnk.connect(wallet).approve(core.address, ONE_THOUSAND_PNK.mul(10), { gasLimit: 300000 });
108133

109-
await setStake(wallet);
134+
await stake(wallet);
110135
}
111136

112137
// Create a dispute
@@ -144,75 +169,227 @@ describe("Draw Benchmark", async () => {
144169
await network.provider.send("evm_mine");
145170
}
146171

147-
await randomizer.relay(rng.address, 0, ethers.utils.randomBytes(32));
148172
await sortitionModule.passPhase(); // Generating -> Drawing
149173

150174
await expectFromDraw(core.draw(0, 1000, { gasLimit: 1000000 }));
175+
176+
await network.provider.send("evm_increaseTime", [2000]); // Wait for maxDrawingTime
177+
await sortitionModule.passPhase(); // Drawing -> Staking
178+
expect(await sortitionModule.phase()).to.equal(Phase.staking);
179+
180+
// Unstake jurors
181+
for (const wallet of wallets) {
182+
await unstake(wallet);
183+
}
184+
};
185+
186+
const countDraws = async (blockNumber: number) => {
187+
const draws: Array<DrawEvent> = await core.queryFilter(core.filters.Draw(), blockNumber, blockNumber);
188+
return draws.reduce((acc: { [address: string]: number }, draw) => {
189+
const address = draw.args._address;
190+
acc[address] = acc[address] ? acc[address] + 1 : 1;
191+
return acc;
192+
}, {});
151193
};
152194

153195
it("Stakes in parent court and should draw jurors in parent court", async () => {
154-
const setStake = async (wallet: Wallet) => {
155-
await core.connect(wallet).setStake(1, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
196+
const stake = async (wallet: Wallet) => {
197+
await core.connect(wallet).setStake(PARENT_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
198+
199+
expect(await core.getJurorBalance(wallet.address, 1)).to.deep.equal([
200+
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
201+
0, // lockedInCourt
202+
PARENT_COURT, // nbOfCourts
203+
]);
156204
};
157-
205+
let countedDraws: CountedDraws;
158206
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
159-
await expect(drawTx)
207+
const tx = await (await drawTx).wait();
208+
expect(tx)
160209
.to.emit(core, "Draw")
161210
.withArgs(anyValue, 0, 0, 0)
162211
.to.emit(core, "Draw")
163212
.withArgs(anyValue, 0, 0, 1)
164213
.to.emit(core, "Draw")
165214
.withArgs(anyValue, 0, 0, 2);
215+
216+
countedDraws = await countDraws(tx.blockNumber);
217+
for (const [address, draws] of Object.entries(countedDraws)) {
218+
expect(await core.getJurorBalance(address, PARENT_COURT)).to.deep.equal([
219+
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
220+
parentCourtMinStake.mul(draws), // lockedInCourt
221+
1, // nbOfCourts
222+
]);
223+
expect(await core.getJurorBalance(address, CHILD_COURT)).to.deep.equal([
224+
0, // stakedInCourt
225+
0, // lockedInCourt
226+
1, // nbOfCourts
227+
]);
228+
}
166229
};
167230

168-
await draw(setStake, "1", expectFromDraw);
231+
const unstake = async (wallet: Wallet) => {
232+
await core.connect(wallet).setStake(PARENT_COURT, 0, { gasLimit: 5000000 });
233+
const locked = parentCourtMinStake.mul(countedDraws[wallet.address] ?? 0);
234+
expect(
235+
await core.getJurorBalance(wallet.address, PARENT_COURT),
236+
"Drawn jurors have a locked stake in the parent court"
237+
).to.deep.equal([
238+
0, // stakedInCourt
239+
locked, // lockedInCourt
240+
0, // nbOfCourts
241+
]);
242+
expect(
243+
await core.getJurorBalance(wallet.address, CHILD_COURT),
244+
"No locked stake in the child court"
245+
).to.deep.equal([
246+
0, // stakedInCourt
247+
0, // lockedInCourt
248+
0, // nbOfCourts
249+
]);
250+
};
251+
252+
await draw(stake, PARENT_COURT, expectFromDraw, unstake);
169253
});
170254

171255
it("Stakes in parent court and should draw nobody in subcourt", async () => {
172-
const setStake = async (wallet: Wallet) => {
173-
await core.connect(wallet).setStake(1, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
256+
const stake = async (wallet: Wallet) => {
257+
await core.connect(wallet).setStake(PARENT_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
174258
};
175259

176260
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
177261
await expect(drawTx).to.not.emit(core, "Draw");
178262
};
179263

180-
await draw(setStake, "2", expectFromDraw);
264+
const unstake = async (wallet: Wallet) => {
265+
await core.connect(wallet).setStake(PARENT_COURT, 0, { gasLimit: 5000000 });
266+
expect(
267+
await core.getJurorBalance(wallet.address, PARENT_COURT),
268+
"No locked stake in the parent court"
269+
).to.deep.equal([
270+
0, // stakedInCourt
271+
0, // lockedInCourt
272+
0, // nbOfCourts
273+
]);
274+
expect(
275+
await core.getJurorBalance(wallet.address, CHILD_COURT),
276+
"No locked stake in the child court"
277+
).to.deep.equal([
278+
0, // stakedInCourt
279+
0, // lockedInCourt
280+
0, // nbOfCourts
281+
]);
282+
};
283+
284+
await draw(stake, CHILD_COURT, expectFromDraw, unstake);
181285
});
182286

183287
it("Stakes in subcourt and should draw jurors in parent court", async () => {
184-
const setStake = async (wallet: Wallet) => {
185-
await core.connect(wallet).setStake(2, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
288+
const stake = async (wallet: Wallet) => {
289+
await core.connect(wallet).setStake(CHILD_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
186290
};
187-
291+
let countedDraws: CountedDraws;
188292
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
189-
await expect(drawTx)
293+
const tx = await (await drawTx).wait();
294+
expect(tx)
190295
.to.emit(core, "Draw")
191296
.withArgs(anyValue, 0, 0, 0)
192297
.to.emit(core, "Draw")
193298
.withArgs(anyValue, 0, 0, 1)
194299
.to.emit(core, "Draw")
195300
.withArgs(anyValue, 0, 0, 2);
301+
302+
countedDraws = await countDraws(tx.blockNumber);
303+
for (const [address, draws] of Object.entries(countedDraws)) {
304+
expect(await core.getJurorBalance(address, PARENT_COURT)).to.deep.equal([
305+
0, // stakedInCourt
306+
parentCourtMinStake.mul(draws), // lockedInCourt
307+
1, // nbOfCourts
308+
]);
309+
expect(await core.getJurorBalance(address, CHILD_COURT)).to.deep.equal([
310+
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
311+
0, // lockedInCourt
312+
1, // nbOfCourts
313+
]);
314+
}
196315
};
197316

198-
await draw(setStake, "1", expectFromDraw);
317+
const unstake = async (wallet: Wallet) => {
318+
await core.connect(wallet).setStake(CHILD_COURT, 0, { gasLimit: 5000000 });
319+
const locked = parentCourtMinStake.mul(countedDraws[wallet.address] ?? 0);
320+
console.log(`draws for ${wallet.address}: ${countedDraws[wallet.address] ?? 0}, locked: ${locked}`);
321+
expect(
322+
await core.getJurorBalance(wallet.address, PARENT_COURT),
323+
"No locked stake in the parent court"
324+
).to.deep.equal([
325+
0, // stakedInCourt
326+
0, // lockedInCourt
327+
0, // nbOfCourts
328+
]);
329+
expect(
330+
await core.getJurorBalance(wallet.address, CHILD_COURT),
331+
"Drawn jurors have a locked stake in the child court"
332+
).to.deep.equal([
333+
0, // stakedInCourt
334+
locked, // lockedInCourt
335+
0, // nbOfCourts
336+
]);
337+
};
338+
339+
await draw(stake, PARENT_COURT, expectFromDraw, unstake);
199340
});
200341

201342
it("Stakes in subcourt and should draw jurors in subcourt", async () => {
202-
const setStake = async (wallet: Wallet) => {
203-
await core.connect(wallet).setStake(2, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
343+
const stake = async (wallet: Wallet) => {
344+
await core.connect(wallet).setStake(CHILD_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
204345
};
205-
346+
let countedDraws: CountedDraws;
206347
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
207-
await expect(drawTx)
348+
const tx = await (await drawTx).wait();
349+
expect(tx)
208350
.to.emit(core, "Draw")
209351
.withArgs(anyValue, 0, 0, 0)
210352
.to.emit(core, "Draw")
211353
.withArgs(anyValue, 0, 0, 1)
212354
.to.emit(core, "Draw")
213355
.withArgs(anyValue, 0, 0, 2);
356+
357+
countedDraws = await countDraws(tx.blockNumber);
358+
for (const [address, draws] of Object.entries(countedDraws)) {
359+
expect(await core.getJurorBalance(address, PARENT_COURT)).to.deep.equal([
360+
0, // stakedInCourt
361+
0, // lockedInCourt
362+
1, // nbOfCourts
363+
]);
364+
expect(await core.getJurorBalance(address, CHILD_COURT)).to.deep.equal([
365+
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
366+
childCourtMinStake.mul(draws), // lockedInCourt
367+
1, // nbOfCourts
368+
]);
369+
}
370+
};
371+
372+
const unstake = async (wallet: Wallet) => {
373+
await core.connect(wallet).setStake(CHILD_COURT, 0, { gasLimit: 5000000 });
374+
const locked = childCourtMinStake.mul(countedDraws[wallet.address] ?? 0);
375+
expect(
376+
await core.getJurorBalance(wallet.address, PARENT_COURT),
377+
"No locked stake in the parent court"
378+
).to.deep.equal([
379+
0, // stakedInCourt
380+
0, // lockedInCourt
381+
0, // nbOfCourts
382+
]);
383+
expect(
384+
await core.getJurorBalance(wallet.address, CHILD_COURT),
385+
"Drawn jurors have a locked stake in the child court"
386+
).to.deep.equal([
387+
0, // stakedInCourt
388+
locked, // lockedInCourt
389+
0, // nbOfCourts
390+
]);
214391
};
215392

216-
await draw(setStake, "2", expectFromDraw);
393+
await draw(stake, CHILD_COURT, expectFromDraw, unstake);
217394
});
218395
});

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"typechain",
3737
"uncommify",
3838
"Unslashed",
39+
"unstake",
3940
"viem",
4041
"wagmi"
4142
],

0 commit comments

Comments
 (0)