Skip to content

Commit 29ef9a1

Browse files
Fix: Update isCurrentRound for previous round on appeal
The isCurrentRound field in the Round entity was not being set to false for the previous round when a new round was created due to an appeal. This commit modifies the handleAppealDecision handler in KlerosCore.ts to: 1. Load the round that was current before the appeal. 2. Set its isCurrentRound field to false and save it. 3. Proceed to create the new round, which will have isCurrentRound set to true by the createRoundFromRoundInfo function. A new test suite, kleros-core.test.ts, was added with a specific test case to verify this behavior. The test ensures that after an appeal: - The previous round's isCurrentRound is false. - The new round's isCurrentRound is true. - The dispute's currentRound and currentRoundIndex are correctly updated. - Court statistics are updated.
1 parent 1ca9066 commit 29ef9a1

File tree

3 files changed

+266
-2
lines changed

3 files changed

+266
-2
lines changed

subgraph/core/src/KlerosCore.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,19 @@ export function handleAppealDecision(event: AppealDecision): void {
215215
const disputeID = event.params._disputeID;
216216
const dispute = Dispute.load(disputeID.toString());
217217
if (!dispute) return;
218+
219+
// Load the current (previous) round
220+
const previousRoundID = dispute.currentRound;
221+
const previousRound = Round.load(previousRoundID);
222+
if (previousRound) {
223+
previousRound.isCurrentRound = false;
224+
previousRound.save();
225+
}
226+
218227
const newRoundIndex = dispute.currentRoundIndex.plus(ONE);
219-
const roundID = `${disputeID}-${newRoundIndex.toString()}`;
228+
const newRoundID = `${disputeID}-${newRoundIndex.toString()}`;
220229
dispute.currentRoundIndex = newRoundIndex;
221-
dispute.currentRound = roundID;
230+
dispute.currentRound = newRoundID;
222231
dispute.save();
223232
const roundInfo = contract.getRoundInfo(disputeID, newRoundIndex);
224233

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { newMockEvent } from "matchstick-as";
2+
import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts";
3+
import { AppealDecision } from "../generated/KlerosCore/KlerosCore";
4+
5+
export function createAppealDecisionEvent(
6+
disputeID: BigInt,
7+
arbitrable: Address,
8+
blockNumber: BigInt,
9+
timestamp: BigInt,
10+
logIndex: BigInt,
11+
transactionHash: string = "0x0000000000000000000000000000000000000000000000000000000000000000" // Default transaction hash
12+
): AppealDecision {
13+
let appealDecisionEvent = changetype<AppealDecision>(newMockEvent());
14+
15+
appealDecisionEvent.parameters = new Array();
16+
appealDecisionEvent.block.number = blockNumber;
17+
appealDecisionEvent.block.timestamp = timestamp;
18+
appealDecisionEvent.logIndex = logIndex;
19+
appealDecisionEvent.transaction.hash = Address.fromString(transactionHash);
20+
21+
22+
appealDecisionEvent.parameters.push(
23+
new ethereum.EventParam("_disputeID", ethereum.Value.fromUnsignedBigInt(disputeID))
24+
);
25+
appealDecisionEvent.parameters.push(
26+
new ethereum.EventParam("_arbitrable", ethereum.Value.fromAddress(arbitrable))
27+
);
28+
29+
return appealDecisionEvent;
30+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import {
2+
assert,
3+
describe,
4+
test,
5+
clearStore,
6+
beforeAll,
7+
afterAll,
8+
createMockedFunction,
9+
logStore, // Useful for debugging
10+
} from "matchstick-as/assembly/index";
11+
import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
12+
import { KlerosCore, AppealDecision } from "../generated/KlerosCore/KlerosCore";
13+
import { Court, Dispute, Round } from "../generated/schema";
14+
import { handleAppealDecision } from "../src/KlerosCore";
15+
import { createAppealDecisionEvent } from "./kleros-core-utils"; // Helper created in previous step
16+
import { ZERO, ONE } from "../src/utils"; // Assuming these are exported from utils
17+
18+
// Default contract address for KlerosCore mock
19+
const KLEROS_CORE_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000001");
20+
const ARBITRABLE_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000002");
21+
const DISPUTE_ID = BigInt.fromI32(0);
22+
const COURT_ID = "0"; // String ID for Court
23+
24+
describe("KlerosCore - handleAppealDecision", () => {
25+
beforeAll(() => {
26+
clearStore(); // Ensure a clean state
27+
28+
// 1. Create and save a Court entity
29+
let court = new Court(COURT_ID);
30+
court.hiddenVotes = false;
31+
court.minStake = BigInt.fromI32(100);
32+
court.alpha = BigInt.fromI32(1000);
33+
court.feeForJuror = BigInt.fromI32(10);
34+
court.jurorsForCourtJump = BigInt.fromI32(3);
35+
court.timesPerPeriod = [
36+
BigInt.fromI32(100),
37+
BigInt.fromI32(100),
38+
BigInt.fromI32(100),
39+
BigInt.fromI32(100),
40+
BigInt.fromI32(100),
41+
];
42+
court.supportedDisputeKits = ["1"];
43+
court.numberDisputes = ZERO;
44+
court.numberClosedDisputes = ZERO;
45+
court.numberAppealingDisputes = ZERO;
46+
court.numberVotingDisputes = ZERO;
47+
court.frozenToken = ZERO;
48+
court.activeJurors = ZERO;
49+
court.inactiveJurors = ZERO;
50+
court.drawnJurors = ZERO;
51+
court.tokenStaked = ZERO;
52+
court.totalCoherentJurors = ZERO;
53+
court.totalResolvedDisputes = ZERO;
54+
court.appealPeriod = [ZERO,ZERO,ZERO,ZERO];
55+
court.numberCoherentVotes = ZERO;
56+
court.numberIncoherentVotes = ZERO;
57+
court.disputesWithoutVotes = ZERO;
58+
court.numberDelayedStakes = ZERO;
59+
court.delayedStakeAmount = ZERO;
60+
court.totalStake = ZERO;
61+
court.numberVotes = ZERO; // Will be updated
62+
court.save();
63+
64+
// 2. Create and save a Dispute entity
65+
let dispute = new Dispute(DISPUTE_ID.toString());
66+
dispute.court = COURT_ID;
67+
dispute.arbitrable = ARBITRABLE_ADDRESS.toHexString();
68+
dispute.numberOfChoices = BigInt.fromI32(2);
69+
dispute.period = "evidence";
70+
dispute.lastPeriodChange = BigInt.fromI32(1000);
71+
dispute.lastPeriodChangeBlockNumber = BigInt.fromI32(1);
72+
dispute.periodNotificationIndex = ZERO;
73+
dispute.periodDeadline = BigInt.fromI32(1100);
74+
dispute.drawsInRound = ZERO;
75+
dispute.commitsInRound = ZERO;
76+
dispute.currentRuling = ZERO;
77+
dispute.tied = true;
78+
dispute.overridden = false;
79+
dispute.currentRoundIndex = ZERO; // Starts at round 0
80+
dispute.currentRound = `${DISPUTE_ID.toString()}-0`; // Initial round ID
81+
dispute.creator = Address.fromString("0x0000000000000000000000000000000000000003").toHexString();
82+
dispute.subcourtID = ZERO;
83+
dispute.category = ZERO;
84+
dispute.jurors = [];
85+
dispute.rewardsFund = ZERO;
86+
dispute.totalFeesForJurors = ZERO;
87+
dispute.totalStaked = ZERO;
88+
dispute.rounds = [];
89+
dispute.ruled = false;
90+
dispute.save();
91+
92+
// 3. Create and save an initial Round entity (round0)
93+
let round0ID = `${DISPUTE_ID.toString()}-0`;
94+
let round0 = new Round(round0ID);
95+
round0.dispute = DISPUTE_ID.toString();
96+
round0.disputeKit = "1"; // Assume classic
97+
round0.court = COURT_ID;
98+
round0.pnkAtStake = BigInt.fromI32(1000);
99+
round0.raisedSoFar = BigInt.fromI32(500);
100+
round0.feeForJuror = BigInt.fromI32(10);
101+
round0.drawnJurors = []; // Initialize as empty
102+
round0.nbVotes = BigInt.fromI32(3); // Number of votes for this round
103+
round0.repartitions = ZERO;
104+
round0.totalAmount = BigInt.fromI32(1500);
105+
round0.deadline = BigInt.fromI32(1200);
106+
round0.isCurrentRound = true; // Crucial for the test
107+
round0.jurorsDrawn = false;
108+
round0.jurorRewardsDispersed = false;
109+
round0.winningChoice = ZERO;
110+
round0.tied = true;
111+
round0.totalVoted = ZERO;
112+
round0.fundedChoices = [];
113+
round0.save();
114+
115+
// Link round0 to dispute's rounds array (if you store it like that)
116+
dispute.rounds = dispute.rounds.concat([round0.id]);
117+
dispute.save();
118+
119+
120+
// 4. Mock contract calls
121+
// Mock KlerosCore.bind()
122+
createMockedFunction(KLEROS_CORE_ADDRESS, "bind", "bind(address):(address)")
123+
.withArgs([ethereum.Value.fromAddress(KLEROS_CORE_ADDRESS)])
124+
.returns([ethereum.Value.fromAddress(KLEROS_CORE_ADDRESS)]);
125+
126+
// Mock contract.disputes(disputeID)
127+
// disputes(uint256) returns (address,uint128,uint128,uint16,uint256,uint8,uint8)
128+
// (courtID, firstRoundID, lastRoundID, numberOfAppeals, jurorsNumber, jurorsForJump, arbitrableChainID)
129+
// We only care about courtID for this handler, which is disputeStorage.value0
130+
let disputeTuple = new ethereum.Tuple();
131+
disputeTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromString(COURT_ID))); // courtID (value0)
132+
disputeTuple.push(ethereum.Value.fromUnsignedBigInt(ZERO)); // firstRoundID
133+
disputeTuple.push(ethereum.Value.fromUnsignedBigInt(ZERO)); // lastRoundID
134+
disputeTuple.push(ethereum.Value.fromUnsignedBigInt(ZERO)); // numberOfAppeals
135+
disputeTuple.push(ethereum.Value.fromUnsignedBigInt(ZERO)); // jurorsNumber
136+
disputeTuple.push(ethereum.Value.fromI32(0)); // jurorsForJump (uint8)
137+
disputeTuple.push(ethereum.Value.fromI32(0)); // arbitrableChainID (uint8)
138+
139+
createMockedFunction(KLEROS_CORE_ADDRESS, "disputes", "disputes(uint256):(uint256,uint128,uint128,uint16,uint256,uint8,uint8)")
140+
.withArgs([ethereum.Value.fromUnsignedBigInt(DISPUTE_ID)])
141+
.returns([ethereum.Value.fromTuple(disputeTuple)]);
142+
143+
// Mock contract.getRoundInfo(disputeID, newRoundIndex) for the *new* round (round 1)
144+
// getRoundInfo(uint256,uint256) returns (uint256,uint256,uint256,address[],uint256,uint256,uint256,uint256)
145+
// (pnkAtStake, raisedSoFar, feeForJuror, drawnJurors, nbVotes, repartitions, totalAmount, deadline)
146+
let newRoundIndex = ONE;
147+
let roundInfoTuple = new ethereum.Tuple();
148+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(2000))); // pnkAtStake for round 1
149+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000))); // raisedSoFar for round 1
150+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(20))); // feeForJuror for round 1
151+
roundInfoTuple.push(ethereum.Value.fromAddressArray([])); // drawnJurors for round 1
152+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(5))); // nbVotes for round 1 (IMPORTANT for court.numberVotes update)
153+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(ZERO)); // repartitions for round 1
154+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(3000))); // totalAmount for round 1
155+
roundInfoTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(2000))); // deadline for round 1
156+
157+
createMockedFunction(KLEROS_CORE_ADDRESS, "getRoundInfo", "getRoundInfo(uint256,uint256):(uint256,uint256,uint256,address[],uint256,uint256,uint256,uint256)")
158+
.withArgs([
159+
ethereum.Value.fromUnsignedBigInt(DISPUTE_ID),
160+
ethereum.Value.fromUnsignedBigInt(newRoundIndex) // newRoundIndex will be 1
161+
])
162+
.returns([ethereum.Value.fromTuple(roundInfoTuple)]);
163+
});
164+
165+
afterAll(() => {
166+
clearStore();
167+
});
168+
169+
test("Should correctly update rounds on appeal decision", () => {
170+
// 1. Create AppealDecision event
171+
let appealTime = BigInt.fromI32(1500);
172+
let appealBlock = BigInt.fromI32(10);
173+
let appealLogIndex = BigInt.fromI32(1);
174+
let appealEvent = createAppealDecisionEvent(
175+
DISPUTE_ID,
176+
ARBITRABLE_ADDRESS,
177+
appealBlock,
178+
appealTime,
179+
appealLogIndex
180+
);
181+
appealEvent.address = KLEROS_CORE_ADDRESS; // Set the event address to match mocked contract
182+
183+
// Store initial court votes
184+
let court = Court.load(COURT_ID)!;
185+
let initialCourtVotes = court.numberVotes;
186+
187+
// 2. Call handleAppealDecision
188+
handleAppealDecision(appealEvent);
189+
190+
// 3. Assertions
191+
// Assert round0.isCurrentRound is false
192+
let round0ID = `${DISPUTE_ID.toString()}-0`;
193+
assert.fieldEquals("Round", round0ID, "isCurrentRound", "false");
194+
195+
// Load the Dispute
196+
let dispute = Dispute.load(DISPUTE_ID.toString())!;
197+
assert.assertNotNull(dispute);
198+
199+
// Assert dispute.currentRoundIndex == ONE
200+
assert.bigIntEquals(ONE, dispute.currentRoundIndex);
201+
202+
// Assert a new Round (round1) is created and isCurrentRound is true
203+
let round1ID = `${DISPUTE_ID.toString()}-1`;
204+
assert.entityCount("Round", 2); // round0 and round1
205+
assert.fieldEquals("Round", round1ID, "isCurrentRound", "true");
206+
assert.fieldEquals("Round", round1ID, "dispute", DISPUTE_ID.toString());
207+
assert.fieldEquals("Round", round1ID, "court", COURT_ID);
208+
// Check some values from mocked getRoundInfo for round1
209+
assert.fieldEquals("Round", round1ID, "pnkAtStake", "2000");
210+
assert.fieldEquals("Round", round1ID, "raisedSoFar", "1000");
211+
assert.fieldEquals("Round", round1ID, "feeForJuror", "20");
212+
assert.fieldEquals("Round", round1ID, "nbVotes", "5"); // From mocked getRoundInfo for new round
213+
214+
// Assert dispute.currentRound points to round1
215+
assert.stringEquals(round1ID, dispute.currentRound);
216+
217+
// Assert court.numberVotes has been updated
218+
// Initial court votes + nbVotes from new round (mocked as 5)
219+
let expectedCourtVotes = initialCourtVotes.plus(BigInt.fromI32(5));
220+
assert.fieldEquals("Court", COURT_ID, "numberVotes", expectedCourtVotes.toString());
221+
222+
// Optional: Log store to see entities - useful for debugging
223+
// logStore();
224+
});
225+
});

0 commit comments

Comments
 (0)