|
| 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