Skip to content

Commit c7a2ad3

Browse files
authored
feat: add usage tracking for app and workerpool (#60)
1 parent a10d31f commit c7a2ad3

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
@@ -11,6 +11,7 @@ import {
1111
test,
1212
} from 'matchstick-as/assembly/index';
1313
import { OrdersMatched } from '../../../generated/Core/IexecInterfaceToken';
14+
import { App, Workerpool } from '../../../generated/schema';
1415
import { handleOrdersMatched } from '../../../src/Modules';
1516
import { toRLC } from '../../../src/utils';
1617
import { EventParamBuilder } from '../utils/EventParamBuilder';
@@ -52,6 +53,29 @@ describe('IexecPoco', () => {
5253
});
5354

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

120311
test('Should handle OrdersMatched with bulk_cid when no dataset', () => {

0 commit comments

Comments
 (0)