Skip to content

Commit 1880a00

Browse files
authored
Merge pull request #34 from kleros/feat/add-counters-to-users
feat: add submitter entity, handlers
2 parents d7e0701 + 0a1164d commit 1880a00

File tree

2 files changed

+98
-25
lines changed

2 files changed

+98
-25
lines changed

schema.graphql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,14 @@ type HasPaidAppealFee @entity {
447447
"Timestamp of the event"
448448
timestamp: BigInt!
449449
}
450+
451+
type Submitter @entity {
452+
"Lower-cased 0x address"
453+
id: ID!
454+
"How many requests this address has ever opened (first time only)."
455+
totalSubmissions: BigInt!
456+
"Open requests = still waiting for a final status."
457+
ongoingSubmissions: BigInt!
458+
"Finished requests (item ended up Absent/Removed or Registered)."
459+
pastSubmissions: BigInt!
460+
}

src/LightGeneralizedTCRMapping.ts

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
EvidenceGroup,
1616
Evidence,
1717
LArbitrator,
18+
Submitter
1819
} from '../generated/schema';
1920
import {
2021
AppealPossible,
@@ -104,6 +105,38 @@ CONTRACT_STATUS_NAMES.set(REGISTERED_CODE, 'Registered');
104105
CONTRACT_STATUS_NAMES.set(REGISTRATION_REQUESTED_CODE, 'RegistrationRequested');
105106
CONTRACT_STATUS_NAMES.set(CLEARING_REQUESTED_CODE, 'ClearingRequested');
106107

108+
/**
109+
* Safely decrement a counter without going below zero.
110+
* Used whenever a registry/item/request counter is reduced.
111+
*/
112+
function safeDecrement(value: BigInt): BigInt {
113+
if (value.gt(BigInt.fromI32(0))) {
114+
return value.minus(BigInt.fromI32(1));
115+
}
116+
return BigInt.fromI32(0);
117+
}
118+
119+
function loadOrCreateSubmitter(addr: Address): Submitter {
120+
let id = addr.toHexString().toLowerCase();
121+
let s = Submitter.load(id);
122+
if (s == null) {
123+
s = new Submitter(id);
124+
s.totalSubmissions = BigInt.fromI32(0);
125+
s.ongoingSubmissions = BigInt.fromI32(0);
126+
s.pastSubmissions = BigInt.fromI32(0);
127+
s.save();
128+
}
129+
return s as Submitter;
130+
}
131+
132+
function moveRequestToPast(requester: Address): void {
133+
let s = loadOrCreateSubmitter(requester);
134+
135+
s.ongoingSubmissions = safeDecrement(s.ongoingSubmissions);
136+
s.pastSubmissions = s.pastSubmissions.plus(BigInt.fromI32(1));
137+
s.save();
138+
}
139+
107140
function getExtendedStatus(disputed: boolean, status: string): number {
108141
if (disputed) {
109142
if (status == CONTRACT_STATUS_NAMES.get(REGISTRATION_REQUESTED_CODE))
@@ -175,23 +208,21 @@ function updateCounters(
175208
}
176209

177210
if (previousStatus == ABSENT_CODE) {
178-
registry.numberOfAbsent = registry.numberOfAbsent.minus(BigInt.fromI32(1));
211+
registry.numberOfAbsent = safeDecrement(registry.numberOfAbsent);
179212
} else if (previousStatus == REGISTERED_CODE) {
180-
registry.numberOfRegistered = registry.numberOfRegistered.minus(
181-
BigInt.fromI32(1),
182-
);
213+
registry.numberOfRegistered = safeDecrement(registry.numberOfRegistered);
183214
} else if (previousStatus == REGISTRATION_REQUESTED_CODE) {
184215
registry.numberOfRegistrationRequested =
185-
registry.numberOfRegistrationRequested.minus(BigInt.fromI32(1));
216+
safeDecrement(registry.numberOfRegistrationRequested);
186217
} else if (previousStatus == CLEARING_REQUESTED_CODE) {
187218
registry.numberOfClearingRequested =
188-
registry.numberOfClearingRequested.minus(BigInt.fromI32(1));
219+
safeDecrement(registry.numberOfClearingRequested);
189220
} else if (previousStatus == CHALLENGED_REGISTRATION_REQUEST_CODE) {
190221
registry.numberOfChallengedRegistrations =
191-
registry.numberOfChallengedRegistrations.minus(BigInt.fromI32(1));
222+
safeDecrement(registry.numberOfChallengedRegistrations);
192223
} else if (previousStatus == CHALLENGED_CLEARING_REQUEST_CODE) {
193224
registry.numberOfChallengedClearing =
194-
registry.numberOfChallengedClearing.minus(BigInt.fromI32(1));
225+
safeDecrement(registry.numberOfChallengedClearing);
195226
}
196227

197228
if (newStatus == ABSENT_CODE) {
@@ -284,6 +315,7 @@ export function handleRequestSubmitted(event: RequestSubmitted): void {
284315
]);
285316
return;
286317
}
318+
287319
// `previousStatus` and `newStatus` are used for accounting.
288320
// Note that if this is the very first request of an item,
289321
// item.status and item.dispute are dirty because they were set by
@@ -300,8 +332,16 @@ export function handleRequestSubmitted(event: RequestSubmitted): void {
300332

301333
let newStatus = getExtendedStatus(item.disputed, item.status);
302334

303-
let requestIndex = item.numberOfRequests.minus(BigInt.fromI32(1));
304-
let requestInfo = tcr.getRequestInfo(event.params._itemID, requestIndex);
335+
let requestIndex = safeDecrement(item.numberOfRequests);
336+
let requestInfoResult = tcr.try_getRequestInfo(event.params._itemID, requestIndex);
337+
if (requestInfoResult.reverted) {
338+
log.error(`Failed to get request info for item {} at request index {}`, [
339+
event.params._itemID.toHexString(),
340+
requestIndex.toString()
341+
]);
342+
return;
343+
}
344+
let requestInfo = requestInfoResult.value;
305345
let requestID = graphItemID + '-' + requestIndex.toString();
306346

307347
let request = new LRequest(requestID);
@@ -359,6 +399,11 @@ export function handleRequestSubmitted(event: RequestSubmitted): void {
359399
updateCounters(previousStatus, newStatus, event.address);
360400
}
361401

402+
let submitter = loadOrCreateSubmitter(Address.fromBytes(request.requester));
403+
submitter.totalSubmissions = submitter.totalSubmissions.plus(BigInt.fromI32(1));
404+
submitter.ongoingSubmissions = submitter.ongoingSubmissions.plus(BigInt.fromI32(1));
405+
submitter.save();
406+
362407
round.save();
363408
request.save();
364409
item.save();
@@ -449,8 +494,16 @@ export function handleRequestChallenged(event: Dispute): void {
449494
item.latestChallenger = event.transaction.from;
450495
let newStatus = getExtendedStatus(item.disputed, item.status);
451496

452-
let requestIndex = item.numberOfRequests.minus(BigInt.fromI32(1));
453-
let requestInfo = tcr.getRequestInfo(itemID, requestIndex);
497+
let requestIndex = safeDecrement(item.numberOfRequests);
498+
let requestInfoResult = tcr.try_getRequestInfo(itemID, requestIndex);
499+
if (requestInfoResult.reverted) {
500+
log.error(`Failed to get request info for item {} at request index {}`, [
501+
itemID.toHexString(),
502+
requestIndex.toString()
503+
]);
504+
return;
505+
}
506+
let requestInfo = requestInfoResult.value;
454507
let requestID = graphItemID + '-' + requestIndex.toString();
455508
let request = LRequest.load(requestID);
456509
if (!request) {
@@ -495,7 +548,7 @@ export function handleAppealPossible(event: AppealPossible): void {
495548
}
496549

497550
let requestID =
498-
item.id + '-' + item.numberOfRequests.minus(BigInt.fromI32(1)).toString();
551+
item.id + '-' + safeDecrement(item.numberOfRequests).toString();
499552
let request = LRequest.load(requestID);
500553
if (!request) {
501554
log.error(`Appeal Possible LRequest {} not found. tx {}`, [
@@ -506,9 +559,7 @@ export function handleAppealPossible(event: AppealPossible): void {
506559
}
507560

508561
let roundID =
509-
request.id +
510-
'-' +
511-
request.numberOfRounds.minus(BigInt.fromI32(1)).toString();
562+
request.id + '-' + safeDecrement(request.numberOfRounds).toString();
512563
let round = LRound.load(roundID);
513564
if (!round) {
514565
log.error(`Appeal Possible LRound {} not found. tx {}`, [
@@ -556,7 +607,7 @@ export function handleAppealDecision(event: AppealDecision): void {
556607
}
557608

558609
let requestID =
559-
item.id + '-' + item.numberOfRequests.minus(BigInt.fromI32(1)).toString();
610+
item.id + '-' + safeDecrement(item.numberOfRequests).toString();
560611
let request = LRequest.load(requestID);
561612
if (!request) {
562613
log.error(`Appeal Decision LRequest {} not found. tx {}`, [
@@ -567,9 +618,7 @@ export function handleAppealDecision(event: AppealDecision): void {
567618
}
568619

569620
let roundID =
570-
request.id +
571-
'-' +
572-
request.numberOfRounds.minus(BigInt.fromI32(1)).toString();
621+
request.id + '-' + safeDecrement(request.numberOfRounds).toString();
573622
let round = LRound.load(roundID);
574623
if (!round) {
575624
log.error(`Appeal Decision LRound {} not found. tx {}`, [
@@ -637,8 +686,16 @@ export function handleStatusUpdated(event: ItemStatusChange): void {
637686

638687
item.latestRequestResolutionTime = event.block.timestamp;
639688

640-
let requestIndex = item.numberOfRequests.minus(BigInt.fromI32(1));
641-
let requestInfo = tcr.getRequestInfo(event.params._itemID, requestIndex);
689+
let requestIndex = safeDecrement(item.numberOfRequests);
690+
let requestInfoResult = tcr.try_getRequestInfo(event.params._itemID, requestIndex);
691+
if (requestInfoResult.reverted) {
692+
log.error(`Failed to get request info for item {} at request index {}`, [
693+
event.params._itemID.toHexString(),
694+
requestIndex.toString()
695+
]);
696+
return;
697+
}
698+
let requestInfo = requestInfoResult.value;
642699

643700
let requestID = graphItemID + '-' + requestIndex.toString();
644701
let request = LRequest.load(requestID);
@@ -653,6 +710,11 @@ export function handleStatusUpdated(event: ItemStatusChange): void {
653710
// requestInfo.value6 is request.ruling.
654711
request.disputeOutcome = getFinalRuling(requestInfo.value6);
655712

713+
if (item.status == REGISTERED || item.status == ABSENT) {
714+
// request just moved to a “finished” final state
715+
moveRequestToPast(Address.fromBytes(request.requester));
716+
}
717+
656718
// Iterate over every contribution and mark it as withdrawable if it is.
657719
// Start from the second round as the first is automatically withdrawn
658720
// when the request resolves.
@@ -695,7 +757,7 @@ export function handleStatusUpdated(event: ItemStatusChange): void {
695757
// the contributors get to withdraw.
696758
if (contribution.side == BigInt.fromI32(REQUESTER_CODE)) {
697759
contribution.withdrawable = true;
698-
} else if (i.equals(request.numberOfRounds.minus(BigInt.fromI32(1)))) {
760+
} else if (i.equals(safeDecrement(request.numberOfRounds))) {
699761
// Contribution was made to the challenger (loser) and this
700762
// is the last round.
701763
contribution.withdrawable = true;
@@ -709,7 +771,7 @@ export function handleStatusUpdated(event: ItemStatusChange): void {
709771
// the contributors get to withdraw.
710772
if (contribution.side == BigInt.fromI32(CHALLENGER_CODE)) {
711773
contribution.withdrawable = true;
712-
} else if (i.equals(request.numberOfRounds.minus(BigInt.fromI32(1)))) {
774+
} else if (i.equals(safeDecrement(request.numberOfRounds))) {
713775
// Contribution was made to the requester (loser) and this
714776
// is the last round.
715777
contribution.withdrawable = true;
@@ -892,7 +954,7 @@ export function handleRuling(event: Ruling): void {
892954
}
893955

894956
let requestID =
895-
item.id + '-' + item.numberOfRequests.minus(BigInt.fromI32(1)).toString();
957+
item.id + '-' + safeDecrement(item.numberOfRequests).toString();
896958
let request = LRequest.load(requestID);
897959
if (!request) {
898960
log.error(`Ruling LRequest {} not found. tx {}`, [

0 commit comments

Comments
 (0)