Skip to content

Commit bdfc7b3

Browse files
committed
feat: add usage tracking for apps and worker pools
- Introduced `usageCount` and `lastUsageTimestamp` fields in the App and Workerpool types. - Implemented functions to fetch App and Workerpool entities. - Updated usage statistics in the `handleOrdersMatched` function. - Initialized usage fields during app and workerpool transfers. - Added unit tests to verify correct accumulation of usage statistics.
1 parent 0462170 commit bdfc7b3

File tree

6 files changed

+227
-0
lines changed

6 files changed

+227
-0
lines changed

schema.graphql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ type App implements Ressource @entity {
136136
checksum: Bytes!
137137
mrenclave: Bytes!
138138
timestamp: BigInt! # last transfer
139+
usageCount: BigInt!
140+
lastUsageTimestamp: BigInt!
139141
usages: [Deal!]! @derivedFrom(field: "app")
140142
orders: [AppOrder!]! @derivedFrom(field: "app")
141143
transfers: [AppTransfer!]! @derivedFrom(field: "app")
@@ -161,6 +163,8 @@ type Workerpool implements Ressource @entity {
161163
workerStakeRatio: BigInt!
162164
schedulerRewardRatio: BigInt!
163165
timestamp: BigInt! # last transfer
166+
usageCount: BigInt!
167+
lastUsageTimestamp: BigInt!
164168
usages: [Deal!]! @derivedFrom(field: "workerpool")
165169
orders: [WorkerpoolOrder!]! @derivedFrom(field: "workerpool")
166170
events: [WorkerpoolEvent!]! @derivedFrom(field: "workerpool")

src/Modules/IexecPoco.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ import {
4747
createContributionID,
4848
createEventID,
4949
fetchAccount,
50+
fetchApp,
5051
fetchApporder,
5152
fetchContribution,
5253
fetchDatasetorder,
5354
fetchDeal,
5455
fetchProtocol,
5556
fetchRequestorder,
5657
fetchTask,
58+
fetchWorkerpool,
5759
fetchWorkerpoolorder,
5860
logTransaction,
5961
toRLC,
@@ -148,6 +150,22 @@ export function handleOrdersMatched(event: OrdersMatchedEvent): void {
148150
requestorder.requester = viewedDeal.requester.toHex();
149151
requestorder.save();
150152

153+
// Update app usage statistics
154+
let app = fetchApp(deal.app);
155+
if (app != null) {
156+
app.usageCount = app.usageCount.plus(deal.botSize);
157+
app.lastUsageTimestamp = event.block.timestamp;
158+
app.save();
159+
}
160+
161+
// Update workerpool usage statistics
162+
let workerpool = fetchWorkerpool(deal.workerpool);
163+
if (workerpool != null) {
164+
workerpool.usageCount = workerpool.usageCount.plus(deal.botSize);
165+
workerpool.lastUsageTimestamp = event.block.timestamp;
166+
workerpool.save();
167+
}
168+
151169
let orderMatchedEvent = new OrdersMatched(createEventID(event));
152170
orderMatchedEvent.transaction = logTransaction(event).id;
153171
orderMatchedEvent.timestamp = event.block.timestamp;

src/Registries/Appregistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export function handleTransferApp(ev: TransferEvent): void {
4747
app.checksum = contract.m_appChecksum();
4848
app.mrenclave = contract.m_appMREnclave();
4949
app.timestamp = ev.block.timestamp;
50+
app.usageCount = BigInt.zero();
51+
app.lastUsageTimestamp = BigInt.zero();
5052
app.save();
5153

5254
let transfer = new AppTransfer(createEventID(ev));

src/Registries/Workerpoolregistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export function handleTransferWorkerpool(ev: TransferEvent): void {
4747
workerpool.workerStakeRatio = contract.m_workerStakeRatioPolicy();
4848
workerpool.schedulerRewardRatio = contract.m_schedulerRewardRatioPolicy();
4949
workerpool.timestamp = ev.block.timestamp;
50+
workerpool.usageCount = BigInt.zero();
51+
workerpool.lastUsageTimestamp = BigInt.zero();
5052
workerpool.save();
5153

5254
let transfer = new WorkerpoolTransfer(createEventID(ev));

src/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313

1414
import {
1515
Account,
16+
App,
1617
AppOrder,
1718
Bulk,
1819
BulkSlice,
@@ -23,6 +24,7 @@ import {
2324
RequestOrder,
2425
Task,
2526
Transaction,
27+
Workerpool,
2628
WorkerpoolOrder,
2729
} from '../generated/schema';
2830

@@ -72,6 +74,14 @@ export function fetchAccount(id: string): Account {
7274
return account as Account;
7375
}
7476

77+
export function fetchApp(id: string): App | null {
78+
return App.load(id);
79+
}
80+
81+
export function fetchWorkerpool(id: string): Workerpool | null {
82+
return Workerpool.load(id);
83+
}
84+
7585
export function fetchDeal(id: string): Deal {
7686
let deal = Deal.load(id);
7787
if (deal == null) {

tests/unit/Modules/IexecPoco.test.ts

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { BigInt, ethereum } from '@graphprotocol/graph-ts';
55
import { assert, describe, newTypedMockEventWithParams, test } from 'matchstick-as/assembly/index';
66
import { OrdersMatched } from '../../../generated/Core/IexecInterfaceToken';
7+
import { App, Workerpool } from '../../../generated/schema';
78
import { handleOrdersMatched } from '../../../src/Modules';
89
import { toRLC } from '../../../src/utils';
910
import { EventParamBuilder } from '../utils/EventParamBuilder';
@@ -41,6 +42,29 @@ const sponsor = mockAddress('sponsor');
4142

4243
describe('IexecPoco', () => {
4344
test('Should handle OrdersMatched', () => {
45+
// Create app and workerpool entities first (they should exist from registry)
46+
let app = new App(appAddress.toHex());
47+
app.owner = appOwner.toHex();
48+
app.name = 'TestApp';
49+
app.type = 'DOCKER';
50+
app.multiaddr = mockBytes32('multiaddr');
51+
app.checksum = mockBytes32('checksum');
52+
app.mrenclave = mockBytes32('mrenclave');
53+
app.timestamp = BigInt.zero();
54+
app.usageCount = BigInt.zero();
55+
app.lastUsageTimestamp = BigInt.zero();
56+
app.save();
57+
58+
let workerpool = new Workerpool(workerpoolAddress.toHex());
59+
workerpool.owner = workerpoolOwner.toHex();
60+
workerpool.description = 'TestWorkerpool';
61+
workerpool.workerStakeRatio = BigInt.fromI32(50);
62+
workerpool.schedulerRewardRatio = BigInt.fromI32(10);
63+
workerpool.timestamp = BigInt.zero();
64+
workerpool.usageCount = BigInt.zero();
65+
workerpool.lastUsageTimestamp = BigInt.zero();
66+
workerpool.save();
67+
4468
mockViewDeal(pocoAddress, dealId).returns([buildDefaultDeal()]);
4569
// Create the mock event
4670
let mockEvent = newTypedMockEventWithParams<OrdersMatched>(
@@ -104,6 +128,173 @@ describe('IexecPoco', () => {
104128
// Assert that a transaction was logged (if applicable)
105129
const transactionId = mockEvent.transaction.hash.toHex();
106130
assert.fieldEquals('Transaction', transactionId, 'id', transactionId);
131+
132+
// Assert that app usage statistics were updated
133+
assert.fieldEquals('App', appAddress.toHex(), 'usageCount', botSize.toString());
134+
assert.fieldEquals('App', appAddress.toHex(), 'lastUsageTimestamp', timestamp.toString());
135+
136+
// Assert that workerpool usage statistics were updated
137+
assert.fieldEquals(
138+
'Workerpool',
139+
workerpoolAddress.toHex(),
140+
'usageCount',
141+
botSize.toString(),
142+
);
143+
assert.fieldEquals(
144+
'Workerpool',
145+
workerpoolAddress.toHex(),
146+
'lastUsageTimestamp',
147+
timestamp.toString(),
148+
);
149+
});
150+
151+
test('Should accumulate usage counts for multiple deals', () => {
152+
const dealId1 = mockBytes32('dealId1');
153+
const dealId2 = mockBytes32('dealId2');
154+
const timestamp1 = BigInt.fromI32(123456789);
155+
const timestamp2 = BigInt.fromI32(123456999);
156+
const botSize1 = BigInt.fromI32(5);
157+
const botSize2 = BigInt.fromI32(3);
158+
159+
// Create app and workerpool entities first (they should exist from registry)
160+
let app = new App(appAddress.toHex());
161+
app.owner = appOwner.toHex();
162+
app.name = 'TestApp';
163+
app.type = 'DOCKER';
164+
app.multiaddr = mockBytes32('multiaddr');
165+
app.checksum = mockBytes32('checksum');
166+
app.mrenclave = mockBytes32('mrenclave');
167+
app.timestamp = BigInt.zero();
168+
app.usageCount = BigInt.zero();
169+
app.lastUsageTimestamp = BigInt.zero();
170+
app.save();
171+
172+
let workerpool = new Workerpool(workerpoolAddress.toHex());
173+
workerpool.owner = workerpoolOwner.toHex();
174+
workerpool.description = 'TestWorkerpool';
175+
workerpool.workerStakeRatio = BigInt.fromI32(50);
176+
workerpool.schedulerRewardRatio = BigInt.fromI32(10);
177+
workerpool.timestamp = BigInt.zero();
178+
workerpool.usageCount = BigInt.zero();
179+
workerpool.lastUsageTimestamp = BigInt.zero();
180+
workerpool.save();
181+
182+
// First deal
183+
mockViewDeal(pocoAddress, dealId1).returns([
184+
buildDeal(
185+
appAddress,
186+
appOwner,
187+
appPrice,
188+
datasetAddress,
189+
datasetOwner,
190+
datasetPrice,
191+
workerpoolAddress,
192+
workerpoolOwner,
193+
workerpoolPrice,
194+
trust,
195+
category,
196+
tag,
197+
requester,
198+
beneficiary,
199+
callback,
200+
params,
201+
startTime,
202+
botFirst,
203+
botSize1,
204+
workerStake,
205+
schedulerRewardRatio,
206+
sponsor,
207+
),
208+
]);
209+
210+
let mockEvent1 = newTypedMockEventWithParams<OrdersMatched>(
211+
EventParamBuilder.init()
212+
.bytes('dealid', dealId1)
213+
.bytes('appHash', appHash)
214+
.bytes('datasetHash', datasetHash)
215+
.bytes('workerpoolHash', workerpoolHash)
216+
.bytes('requestHash', requestHash)
217+
.build(),
218+
);
219+
mockEvent1.block.timestamp = timestamp1;
220+
mockEvent1.address = pocoAddress;
221+
222+
handleOrdersMatched(mockEvent1);
223+
224+
// Verify first deal usage
225+
assert.fieldEquals('App', appAddress.toHex(), 'usageCount', botSize1.toString());
226+
assert.fieldEquals('App', appAddress.toHex(), 'lastUsageTimestamp', timestamp1.toString());
227+
assert.fieldEquals(
228+
'Workerpool',
229+
workerpoolAddress.toHex(),
230+
'usageCount',
231+
botSize1.toString(),
232+
);
233+
assert.fieldEquals(
234+
'Workerpool',
235+
workerpoolAddress.toHex(),
236+
'lastUsageTimestamp',
237+
timestamp1.toString(),
238+
);
239+
240+
// Second deal
241+
mockViewDeal(pocoAddress, dealId2).returns([
242+
buildDeal(
243+
appAddress,
244+
appOwner,
245+
appPrice,
246+
datasetAddress,
247+
datasetOwner,
248+
datasetPrice,
249+
workerpoolAddress,
250+
workerpoolOwner,
251+
workerpoolPrice,
252+
trust,
253+
category,
254+
tag,
255+
requester,
256+
beneficiary,
257+
callback,
258+
params,
259+
startTime,
260+
botFirst,
261+
botSize2,
262+
workerStake,
263+
schedulerRewardRatio,
264+
sponsor,
265+
),
266+
]);
267+
268+
let mockEvent2 = newTypedMockEventWithParams<OrdersMatched>(
269+
EventParamBuilder.init()
270+
.bytes('dealid', dealId2)
271+
.bytes('appHash', mockBytes32('appHash2'))
272+
.bytes('datasetHash', mockBytes32('datasetHash2'))
273+
.bytes('workerpoolHash', mockBytes32('workerpoolHash2'))
274+
.bytes('requestHash', mockBytes32('requestHash2'))
275+
.build(),
276+
);
277+
mockEvent2.block.timestamp = timestamp2;
278+
mockEvent2.address = pocoAddress;
279+
280+
handleOrdersMatched(mockEvent2);
281+
282+
// Verify accumulated usage
283+
const totalUsage = botSize1.plus(botSize2);
284+
assert.fieldEquals('App', appAddress.toHex(), 'usageCount', totalUsage.toString());
285+
assert.fieldEquals('App', appAddress.toHex(), 'lastUsageTimestamp', timestamp2.toString());
286+
assert.fieldEquals(
287+
'Workerpool',
288+
workerpoolAddress.toHex(),
289+
'usageCount',
290+
totalUsage.toString(),
291+
);
292+
assert.fieldEquals(
293+
'Workerpool',
294+
workerpoolAddress.toHex(),
295+
'lastUsageTimestamp',
296+
timestamp2.toString(),
297+
);
107298
});
108299
});
109300

0 commit comments

Comments
 (0)