Skip to content

Commit

Permalink
test: reproduction of the staking and locked stakes bug
Browse files Browse the repository at this point in the history
  • Loading branch information
jaybuidl committed Sep 9, 2023
1 parent 6b6eaf4 commit 8cb7245
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 28 deletions.
233 changes: 205 additions & 28 deletions contracts/test/arbitration/draw.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
import { deployments, ethers, getNamedAccounts, network } from "hardhat";
import { BigNumber, ContractTransaction, Wallet } from "ethers";
import { BigNumber, ContractReceipt, ContractTransaction, Wallet } from "ethers";
import {
PNK,
KlerosCore,
ArbitrableExample,
HomeGateway,
DisputeKitClassic,
RandomizerRNG,
RandomizerMock,
SortitionModule,
} from "../../typechain-types";
import { expect } from "chai";
import { DrawEvent } from "../../typechain-types/src/kleros-v1/kleros-liquid-xdai/XKlerosLiquidV2";

/* eslint-disable no-unused-vars */
/* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482
Expand Down Expand Up @@ -49,7 +48,11 @@ describe("Draw Benchmark", async () => {
let homeGateway;
let sortitionModule;
let rng;
let randomizer;
let parentCourtMinStake: BigNumber;
let childCourtMinStake: BigNumber;
const RANDOM = BigNumber.from("61688911660239508166491237672720926005752254046266901728404745669596507231249");
const PARENT_COURT = 1;
const CHILD_COURT = 2;

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

// CourtId 2
parentCourtMinStake = await core
.GENERAL_COURT()
.then((courtId) => core.courts(courtId))
.then((court) => court.minStake);

childCourtMinStake = BigNumber.from(10).pow(20).mul(3); // 300 PNK

// Make the tests more deterministic with this dummy RNG
rng = await deployments.deploy("IncrementalNG", {
from: deployer,
args: [RANDOM],
log: true,
});

await sortitionModule.changeRandomNumberGenerator(rng.address, 20);

// CourtId 2 = CHILD_COURT
const minStake = BigNumber.from(10).pow(20).mul(3); // 300 PNK
const alpha = 10000;
const feeForJuror = BigNumber.from(10).pow(17);
Expand All @@ -84,16 +101,24 @@ describe("Draw Benchmark", async () => {
);
});

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

const draw = async (setStake: SetStake, createDisputeCourtId: string, expectFromDraw: ExpectFromDraw) => {
const draw = async (
stake: SetStake,
createDisputeCourtId: number,
expectFromDraw: ExpectFromDraw,
unstake: SetStake
) => {
const arbitrationCost = ONE_TENTH_ETH.mul(3);
const [bridger] = await ethers.getSigners();
const wallets: Wallet[] = [];

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

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

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

await setStake(wallet);
await stake(wallet);
}

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

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

await expectFromDraw(core.draw(0, 1000, { gasLimit: 1000000 }));

await network.provider.send("evm_increaseTime", [2000]); // Wait for maxDrawingTime
await sortitionModule.passPhase(); // Drawing -> Staking
expect(await sortitionModule.phase()).to.equal(Phase.staking);

// Unstake jurors
for (const wallet of wallets) {
await unstake(wallet);
}
};

const countDraws = async (blockNumber: number) => {
const draws: Array<DrawEvent> = await core.queryFilter(core.filters.Draw(), blockNumber, blockNumber);
return draws.reduce((acc: { [address: string]: number }, draw) => {
const address = draw.args._address;
acc[address] = acc[address] ? acc[address] + 1 : 1;
return acc;
}, {});
};

it("Stakes in parent court and should draw jurors in parent court", async () => {
const setStake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(1, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
const stake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(PARENT_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });

expect(await core.getJurorBalance(wallet.address, 1)).to.deep.equal([
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
0, // lockedInCourt
PARENT_COURT, // nbOfCourts
]);
};

let countedDraws: CountedDraws;
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
await expect(drawTx)
const tx = await (await drawTx).wait();
expect(tx)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 0)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 1)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 2);

countedDraws = await countDraws(tx.blockNumber);
for (const [address, draws] of Object.entries(countedDraws)) {
expect(await core.getJurorBalance(address, PARENT_COURT)).to.deep.equal([
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
parentCourtMinStake.mul(draws), // lockedInCourt
1, // nbOfCourts
]);
expect(await core.getJurorBalance(address, CHILD_COURT)).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
1, // nbOfCourts
]);
}
};

await draw(setStake, "1", expectFromDraw);
const unstake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(PARENT_COURT, 0, { gasLimit: 5000000 });
const locked = parentCourtMinStake.mul(countedDraws[wallet.address] ?? 0);
expect(
await core.getJurorBalance(wallet.address, PARENT_COURT),
"Drawn jurors have a locked stake in the parent court"
).to.deep.equal([
0, // stakedInCourt
locked, // lockedInCourt
0, // nbOfCourts
]);
expect(
await core.getJurorBalance(wallet.address, CHILD_COURT),
"No locked stake in the child court"
).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
0, // nbOfCourts
]);
};

await draw(stake, PARENT_COURT, expectFromDraw, unstake);
});

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

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

await draw(setStake, "2", expectFromDraw);
const unstake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(PARENT_COURT, 0, { gasLimit: 5000000 });
expect(
await core.getJurorBalance(wallet.address, PARENT_COURT),
"No locked stake in the parent court"
).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
0, // nbOfCourts
]);
expect(
await core.getJurorBalance(wallet.address, CHILD_COURT),
"No locked stake in the child court"
).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
0, // nbOfCourts
]);
};

await draw(stake, CHILD_COURT, expectFromDraw, unstake);
});

it("Stakes in subcourt and should draw jurors in parent court", async () => {
const setStake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(2, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
const stake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(CHILD_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
};

let countedDraws: CountedDraws;
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
await expect(drawTx)
const tx = await (await drawTx).wait();
expect(tx)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 0)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 1)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 2);

countedDraws = await countDraws(tx.blockNumber);
for (const [address, draws] of Object.entries(countedDraws)) {
expect(await core.getJurorBalance(address, PARENT_COURT)).to.deep.equal([
0, // stakedInCourt
parentCourtMinStake.mul(draws), // lockedInCourt
1, // nbOfCourts
]);
expect(await core.getJurorBalance(address, CHILD_COURT)).to.deep.equal([
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
0, // lockedInCourt
1, // nbOfCourts
]);
}
};

await draw(setStake, "1", expectFromDraw);
const unstake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(CHILD_COURT, 0, { gasLimit: 5000000 });
const locked = parentCourtMinStake.mul(countedDraws[wallet.address] ?? 0);
console.log(`draws for ${wallet.address}: ${countedDraws[wallet.address] ?? 0}, locked: ${locked}`);
expect(
await core.getJurorBalance(wallet.address, PARENT_COURT),
"No locked stake in the parent court"
).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
0, // nbOfCourts
]);
expect(
await core.getJurorBalance(wallet.address, CHILD_COURT),
"Drawn jurors have a locked stake in the child court"
).to.deep.equal([
0, // stakedInCourt
locked, // lockedInCourt
0, // nbOfCourts
]);
};

await draw(stake, PARENT_COURT, expectFromDraw, unstake);
});

it("Stakes in subcourt and should draw jurors in subcourt", async () => {
const setStake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(2, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
const stake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(CHILD_COURT, ONE_THOUSAND_PNK.mul(5), { gasLimit: 5000000 });
};

let countedDraws: CountedDraws;
const expectFromDraw = async (drawTx: Promise<ContractTransaction>) => {
await expect(drawTx)
const tx = await (await drawTx).wait();
expect(tx)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 0)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 1)
.to.emit(core, "Draw")
.withArgs(anyValue, 0, 0, 2);

countedDraws = await countDraws(tx.blockNumber);
for (const [address, draws] of Object.entries(countedDraws)) {
expect(await core.getJurorBalance(address, PARENT_COURT)).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
1, // nbOfCourts
]);
expect(await core.getJurorBalance(address, CHILD_COURT)).to.deep.equal([
ONE_THOUSAND_PNK.mul(5), // stakedInCourt
childCourtMinStake.mul(draws), // lockedInCourt
1, // nbOfCourts
]);
}
};

const unstake = async (wallet: Wallet) => {
await core.connect(wallet).setStake(CHILD_COURT, 0, { gasLimit: 5000000 });
const locked = childCourtMinStake.mul(countedDraws[wallet.address] ?? 0);
expect(
await core.getJurorBalance(wallet.address, PARENT_COURT),
"No locked stake in the parent court"
).to.deep.equal([
0, // stakedInCourt
0, // lockedInCourt
0, // nbOfCourts
]);
expect(
await core.getJurorBalance(wallet.address, CHILD_COURT),
"Drawn jurors have a locked stake in the child court"
).to.deep.equal([
0, // stakedInCourt
locked, // lockedInCourt
0, // nbOfCourts
]);
};

await draw(setStake, "2", expectFromDraw);
await draw(stake, CHILD_COURT, expectFromDraw, unstake);
});
});
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"typechain",
"uncommify",
"Unslashed",
"unstake",
"viem",
"wagmi"
],
Expand Down

0 comments on commit 8cb7245

Please sign in to comment.