Skip to content

Commit dfc22a2

Browse files
committed
test: delayed stakes
1 parent 1b365a4 commit dfc22a2

File tree

4 files changed

+239
-88
lines changed

4 files changed

+239
-88
lines changed

contracts/.mocharc.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

contracts/hardhat.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ const config: HardhatUserConfig = {
302302
clear: true,
303303
runOnCompile: false,
304304
},
305+
mocha: {
306+
timeout: 20000,
307+
},
305308
tenderly: {
306309
project: process.env.TENDERLY_PROJECT !== undefined ? process.env.TENDERLY_PROJECT : "kleros-v2",
307310
username: process.env.TENDERLY_USERNAME !== undefined ? process.env.TENDERLY_USERNAME : "",

contracts/test/arbitration/staking.ts

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import { ethers, getNamedAccounts, network, deployments } from "hardhat";
2+
import { BigNumber } from "ethers";
3+
import {
4+
PNK,
5+
KlerosCore,
6+
DisputeKitClassic,
7+
SortitionModule,
8+
RandomizerRNG,
9+
RandomizerMock,
10+
} from "../../typechain-types";
11+
import { expect } from "chai";
12+
import exp from "constants";
13+
14+
/* eslint-disable no-unused-vars */
15+
/* eslint-disable no-unused-expressions */
16+
17+
describe("Staking", async () => {
18+
const ONE_TENTH_ETH = BigNumber.from(10).pow(17);
19+
const ONE_THOUSAND_PNK = BigNumber.from(10).pow(21);
20+
21+
// 2nd court, 3 jurors, 1 dispute kit
22+
const extraData =
23+
"0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001";
24+
25+
let deployer;
26+
let disputeKit;
27+
let pnk;
28+
let core;
29+
let sortition;
30+
let rng;
31+
let randomizer;
32+
33+
const deploy = async () => {
34+
({ deployer } = await getNamedAccounts());
35+
await deployments.fixture(["Arbitration"], {
36+
fallbackToGlobal: true,
37+
keepExistingDeployments: false,
38+
});
39+
disputeKit = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic;
40+
pnk = (await ethers.getContract("PNK")) as PNK;
41+
core = (await ethers.getContract("KlerosCore")) as KlerosCore;
42+
sortition = (await ethers.getContract("SortitionModule")) as SortitionModule;
43+
rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
44+
randomizer = (await ethers.getContract("RandomizerMock")) as RandomizerMock;
45+
};
46+
47+
describe("When outside the Staking phase", async () => {
48+
let balanceBefore;
49+
50+
const reachDrawingPhase = async () => {
51+
expect(await sortition.phase()).to.be.equal(0); // Staking
52+
const arbitrationCost = ONE_TENTH_ETH.mul(3);
53+
54+
await core.createCourt(1, false, ONE_THOUSAND_PNK, 1000, ONE_TENTH_ETH, 3, [0, 0, 0, 0], 3, [1]); // Parent - general court, Classic dispute kit
55+
56+
await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(4));
57+
await core.setStake(1, ONE_THOUSAND_PNK.mul(2));
58+
await core.setStake(2, ONE_THOUSAND_PNK.mul(2));
59+
60+
expect(await core.getJurorCourtIDs(deployer)).to.be.deep.equal([BigNumber.from("1"), BigNumber.from("2")]);
61+
62+
await core.functions["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost });
63+
64+
await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
65+
await network.provider.send("evm_mine");
66+
67+
const lookahead = await sortition.rngLookahead();
68+
await sortition.passPhase(); // Staking -> Generating
69+
for (let index = 0; index < lookahead; index++) {
70+
await network.provider.send("evm_mine");
71+
}
72+
73+
balanceBefore = await pnk.balanceOf(deployer);
74+
};
75+
76+
describe("When decreasing then increasing back stake", async () => {
77+
before("Setup", async () => {
78+
await deploy();
79+
await reachDrawingPhase();
80+
});
81+
82+
it("Should be outside the Staking phase", async () => {
83+
expect(await sortition.phase()).to.be.equal(1); // Drawing
84+
expect(await core.getJurorBalance(deployer, 2)).to.be.deep.equal([
85+
ONE_THOUSAND_PNK.mul(4),
86+
BigNumber.from(0),
87+
ONE_THOUSAND_PNK.mul(2),
88+
BigNumber.from(2),
89+
]);
90+
});
91+
92+
it("Should delay the stake decrease", async () => {
93+
expect(await sortition.delayedStakeWriteIndex()).to.be.equal(0);
94+
expect(await sortition.delayedStakeReadIndex()).to.be.equal(1);
95+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(0);
96+
await expect(core.setStake(2, ONE_THOUSAND_PNK.mul(1)))
97+
.to.emit(core, "StakeDelayed")
98+
.withArgs(deployer, 2, ONE_THOUSAND_PNK.mul(1));
99+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(1);
100+
expect(await core.getJurorBalance(deployer, 2)).to.be.deep.equal([
101+
ONE_THOUSAND_PNK.mul(4),
102+
BigNumber.from(0),
103+
ONE_THOUSAND_PNK.mul(2),
104+
BigNumber.from(2),
105+
]); // stake unchanged, delayed
106+
expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore); // No PNK transfer yet
107+
expect(await sortition.delayedStakeWriteIndex()).to.be.equal(1);
108+
expect(await sortition.delayedStakeReadIndex()).to.be.equal(1);
109+
expect(await sortition.delayedStakes(1)).to.be.deep.equal([deployer, 2, ONE_THOUSAND_PNK.mul(1), false]);
110+
});
111+
112+
it("Should delay the stake increase back to the previous amount", async () => {
113+
balanceBefore = await pnk.balanceOf(deployer);
114+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(1);
115+
await expect(core.setStake(2, ONE_THOUSAND_PNK.mul(2)))
116+
.to.emit(core, "StakeDelayed")
117+
.withArgs(deployer, 2, ONE_THOUSAND_PNK.mul(2));
118+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(2);
119+
expect(await core.getJurorBalance(deployer, 2)).to.be.deep.equal([
120+
ONE_THOUSAND_PNK.mul(4),
121+
BigNumber.from(0),
122+
ONE_THOUSAND_PNK.mul(2),
123+
BigNumber.from(2),
124+
]); // stake unchanged, delayed
125+
expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore); // No PNK transfer yet
126+
expect(await sortition.delayedStakeWriteIndex()).to.be.equal(2);
127+
expect(await sortition.delayedStakeReadIndex()).to.be.equal(1);
128+
expect(await sortition.delayedStakes(1)).to.be.deep.equal([ethers.constants.AddressZero, 0, 0, false]); // the 1st delayed stake got deleted
129+
expect(await sortition.delayedStakes(2)).to.be.deep.equal([deployer, 2, ONE_THOUSAND_PNK.mul(2), false]);
130+
});
131+
});
132+
133+
describe("When increasing then decreasing back stake", async () => {
134+
before("Setup", async () => {
135+
await deploy();
136+
await reachDrawingPhase();
137+
});
138+
139+
it("Should be outside the Staking phase", async () => {
140+
expect(await sortition.phase()).to.be.equal(1); // Drawing
141+
expect(await core.getJurorBalance(deployer, 2)).to.be.deep.equal([
142+
ONE_THOUSAND_PNK.mul(4),
143+
BigNumber.from(0),
144+
ONE_THOUSAND_PNK.mul(2),
145+
BigNumber.from(2),
146+
]);
147+
});
148+
149+
it("Should transfer PNK but delay the stake increase", async () => {
150+
expect(await sortition.delayedStakeWriteIndex()).to.be.equal(0);
151+
expect(await sortition.delayedStakeReadIndex()).to.be.equal(1);
152+
await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(1));
153+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(0);
154+
await expect(core.setStake(2, ONE_THOUSAND_PNK.mul(3)))
155+
.to.emit(core, "StakePartiallyDelayed")
156+
.withArgs(deployer, 2, ONE_THOUSAND_PNK.mul(3));
157+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(1);
158+
expect(await core.getJurorBalance(deployer, 2)).to.be.deep.equal([
159+
ONE_THOUSAND_PNK.mul(5),
160+
BigNumber.from(0),
161+
ONE_THOUSAND_PNK.mul(3),
162+
BigNumber.from(2),
163+
]); // stake has changed immediately, WARNING: this is misleading because it's not actually added to the SortitionSumTree
164+
expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore.sub(ONE_THOUSAND_PNK)); // PNK is transferred out of the juror's account
165+
expect(await sortition.delayedStakeWriteIndex()).to.be.equal(1);
166+
expect(await sortition.delayedStakeReadIndex()).to.be.equal(1);
167+
expect(await sortition.delayedStakes(1)).to.be.deep.equal([deployer, 2, ONE_THOUSAND_PNK.mul(3), true]);
168+
});
169+
170+
it("Should cancel out the stake decrease back", async () => {
171+
balanceBefore = await pnk.balanceOf(deployer);
172+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(1);
173+
await expect(core.setStake(2, ONE_THOUSAND_PNK.mul(2)))
174+
.to.emit(core, "StakeDelayed")
175+
.withArgs(deployer, 2, ONE_THOUSAND_PNK.mul(2));
176+
expect(await sortition.latestDelayedStakeIndex(deployer, 2)).to.be.equal(2);
177+
expect(await core.getJurorBalance(deployer, 2)).to.be.deep.equal([
178+
ONE_THOUSAND_PNK.mul(4),
179+
BigNumber.from(0),
180+
ONE_THOUSAND_PNK.mul(2),
181+
BigNumber.from(2),
182+
]); // stake has changed immediately
183+
expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore.add(ONE_THOUSAND_PNK)); // PNK is sent back to the juror
184+
expect(await sortition.delayedStakeWriteIndex()).to.be.equal(2);
185+
expect(await sortition.delayedStakeReadIndex()).to.be.equal(1);
186+
expect(await sortition.delayedStakes(1)).to.be.deep.equal([ethers.constants.AddressZero, 0, 0, false]); // the 1st delayed stake got deleted
187+
expect(await sortition.delayedStakes(2)).to.be.deep.equal([deployer, 2, ONE_THOUSAND_PNK.mul(2), false]);
188+
});
189+
});
190+
});
191+
192+
describe("When a juror is inactive", async () => {
193+
before("Setup", async () => {
194+
await deploy();
195+
});
196+
197+
it("Should unstake from all courts", async () => {
198+
const arbitrationCost = ONE_TENTH_ETH.mul(3);
199+
200+
await core.createCourt(1, false, ONE_THOUSAND_PNK, 1000, ONE_TENTH_ETH, 3, [0, 0, 0, 0], 3, [1]); // Parent - general court, Classic dispute kit
201+
202+
await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(4));
203+
await core.setStake(1, ONE_THOUSAND_PNK.mul(2));
204+
await core.setStake(2, ONE_THOUSAND_PNK.mul(2));
205+
206+
expect(await core.getJurorCourtIDs(deployer)).to.be.deep.equal([BigNumber.from("1"), BigNumber.from("2")]);
207+
208+
await core.functions["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost });
209+
210+
await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
211+
await network.provider.send("evm_mine");
212+
213+
const lookahead = await sortition.rngLookahead();
214+
await sortition.passPhase(); // Staking -> Generating
215+
for (let index = 0; index < lookahead; index++) {
216+
await network.provider.send("evm_mine");
217+
}
218+
await randomizer.relay(rng.address, 0, ethers.utils.randomBytes(32));
219+
await sortition.passPhase(); // Generating -> Drawing
220+
221+
await core.draw(0, 5000);
222+
223+
await core.passPeriod(0); // Evidence -> Voting
224+
await core.passPeriod(0); // Voting -> Appeal
225+
await core.passPeriod(0); // Appeal -> Execution
226+
227+
await sortition.passPhase(); // Freezing -> Staking. Change so we don't deal with delayed stakes
228+
229+
expect(await core.getJurorCourtIDs(deployer)).to.be.deep.equal([BigNumber.from("1"), BigNumber.from("2")]);
230+
231+
await core.execute(0, 0, 1); // 1 iteration should unstake from both courts
232+
233+
expect(await core.getJurorCourtIDs(deployer)).to.be.deep.equal([]);
234+
});
235+
});
236+
});

contracts/test/arbitration/unstake.ts

Lines changed: 0 additions & 84 deletions
This file was deleted.

0 commit comments

Comments
 (0)