Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ type App implements Ressource @entity {
checksum: Bytes!
mrenclave: Bytes!
timestamp: BigInt! # last transfer
usageCount: BigInt!
lastUsageTimestamp: BigInt!
usages: [Deal!]! @derivedFrom(field: "app")
orders: [AppOrder!]! @derivedFrom(field: "app")
transfers: [AppTransfer!]! @derivedFrom(field: "app")
Expand All @@ -161,6 +163,8 @@ type Workerpool implements Ressource @entity {
workerStakeRatio: BigInt!
schedulerRewardRatio: BigInt!
timestamp: BigInt! # last transfer
usageCount: BigInt!
lastUsageTimestamp: BigInt!
usages: [Deal!]! @derivedFrom(field: "workerpool")
orders: [WorkerpoolOrder!]! @derivedFrom(field: "workerpool")
events: [WorkerpoolEvent!]! @derivedFrom(field: "workerpool")
Expand Down
18 changes: 18 additions & 0 deletions src/Modules/IexecPoco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ import {
createContributionID,
createEventID,
fetchAccount,
fetchApp,
fetchApporder,
fetchContribution,
fetchDatasetorder,
fetchDeal,
fetchProtocol,
fetchRequestorder,
fetchTask,
fetchWorkerpool,
fetchWorkerpoolorder,
logTransaction,
toRLC,
Expand Down Expand Up @@ -148,6 +150,22 @@ export function handleOrdersMatched(event: OrdersMatchedEvent): void {
requestorder.requester = viewedDeal.requester.toHex();
requestorder.save();

// Update app usage statistics
let app = fetchApp(deal.app);
if (app != null) {
app.usageCount = app.usageCount.plus(deal.botSize);
app.lastUsageTimestamp = event.block.timestamp;
app.save();
}

// Update workerpool usage statistics
let workerpool = fetchWorkerpool(deal.workerpool);
if (workerpool != null) {
workerpool.usageCount = workerpool.usageCount.plus(deal.botSize);
workerpool.lastUsageTimestamp = event.block.timestamp;
workerpool.save();
}

let orderMatchedEvent = new OrdersMatched(createEventID(event));
orderMatchedEvent.transaction = logTransaction(event).id;
orderMatchedEvent.timestamp = event.block.timestamp;
Expand Down
2 changes: 2 additions & 0 deletions src/Registries/Appregistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export function handleTransferApp(ev: TransferEvent): void {
app.checksum = contract.m_appChecksum();
app.mrenclave = contract.m_appMREnclave();
app.timestamp = ev.block.timestamp;
app.usageCount = BigInt.zero();
app.lastUsageTimestamp = BigInt.zero();
app.save();

let transfer = new AppTransfer(createEventID(ev));
Expand Down
2 changes: 2 additions & 0 deletions src/Registries/Workerpoolregistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export function handleTransferWorkerpool(ev: TransferEvent): void {
workerpool.workerStakeRatio = contract.m_workerStakeRatioPolicy();
workerpool.schedulerRewardRatio = contract.m_schedulerRewardRatioPolicy();
workerpool.timestamp = ev.block.timestamp;
workerpool.usageCount = BigInt.zero();
workerpool.lastUsageTimestamp = BigInt.zero();
workerpool.save();

let transfer = new WorkerpoolTransfer(createEventID(ev));
Expand Down
10 changes: 10 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {

import {
Account,
App,
AppOrder,
Bulk,
BulkSlice,
Expand All @@ -23,6 +24,7 @@ import {
RequestOrder,
Task,
Transaction,
Workerpool,
WorkerpoolOrder,
} from '../generated/schema';

Expand Down Expand Up @@ -72,6 +74,14 @@ export function fetchAccount(id: string): Account {
return account as Account;
}

export function fetchApp(id: string): App | null {
return App.load(id);
}

export function fetchWorkerpool(id: string): Workerpool | null {
return Workerpool.load(id);
}

export function fetchDeal(id: string): Deal {
let deal = Deal.load(id);
if (deal == null) {
Expand Down
191 changes: 191 additions & 0 deletions tests/unit/Modules/IexecPoco.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
test,
} from 'matchstick-as/assembly/index';
import { OrdersMatched } from '../../../generated/Core/IexecInterfaceToken';
import { App, Workerpool } from '../../../generated/schema';
import { handleOrdersMatched } from '../../../src/Modules';
import { toRLC } from '../../../src/utils';
import { EventParamBuilder } from '../utils/EventParamBuilder';
Expand Down Expand Up @@ -52,6 +53,29 @@ describe('IexecPoco', () => {
});

test('Should handle OrdersMatched', () => {
// Create app and workerpool entities first (they should exist from registry)
let app = new App(appAddress.toHex());
app.owner = appOwner.toHex();
app.name = 'TestApp';
app.type = 'DOCKER';
app.multiaddr = mockBytes32('multiaddr');
app.checksum = mockBytes32('checksum');
app.mrenclave = mockBytes32('mrenclave');
app.timestamp = BigInt.zero();
app.usageCount = BigInt.zero();
app.lastUsageTimestamp = BigInt.zero();
app.save();

let workerpool = new Workerpool(workerpoolAddress.toHex());
workerpool.owner = workerpoolOwner.toHex();
workerpool.description = 'TestWorkerpool';
workerpool.workerStakeRatio = BigInt.fromI32(50);
workerpool.schedulerRewardRatio = BigInt.fromI32(10);
workerpool.timestamp = BigInt.zero();
workerpool.usageCount = BigInt.zero();
workerpool.lastUsageTimestamp = BigInt.zero();
workerpool.save();

mockViewDeal(pocoAddress, dealId).returns([buildDefaultDeal()]);
// Create the mock event
let mockEvent = newTypedMockEventWithParams<OrdersMatched>(
Expand Down Expand Up @@ -115,6 +139,173 @@ describe('IexecPoco', () => {
// Assert that a transaction was logged (if applicable)
const transactionId = mockEvent.transaction.hash.toHex();
assert.fieldEquals('Transaction', transactionId, 'id', transactionId);

// Assert that app usage statistics were updated
assert.fieldEquals('App', appAddress.toHex(), 'usageCount', botSize.toString());
assert.fieldEquals('App', appAddress.toHex(), 'lastUsageTimestamp', timestamp.toString());

// Assert that workerpool usage statistics were updated
assert.fieldEquals(
'Workerpool',
workerpoolAddress.toHex(),
'usageCount',
botSize.toString(),
);
assert.fieldEquals(
'Workerpool',
workerpoolAddress.toHex(),
'lastUsageTimestamp',
timestamp.toString(),
);
});

test('Should accumulate usage counts for multiple deals', () => {
const dealId1 = mockBytes32('dealId1');
const dealId2 = mockBytes32('dealId2');
const timestamp1 = BigInt.fromI32(123456789);
const timestamp2 = BigInt.fromI32(123456999);
const botSize1 = BigInt.fromI32(5);
const botSize2 = BigInt.fromI32(3);

// Create app and workerpool entities first (they should exist from registry)
let app = new App(appAddress.toHex());
app.owner = appOwner.toHex();
app.name = 'TestApp';
app.type = 'DOCKER';
app.multiaddr = mockBytes32('multiaddr');
app.checksum = mockBytes32('checksum');
app.mrenclave = mockBytes32('mrenclave');
app.timestamp = BigInt.zero();
app.usageCount = BigInt.zero();
app.lastUsageTimestamp = BigInt.zero();
app.save();

let workerpool = new Workerpool(workerpoolAddress.toHex());
workerpool.owner = workerpoolOwner.toHex();
workerpool.description = 'TestWorkerpool';
workerpool.workerStakeRatio = BigInt.fromI32(50);
workerpool.schedulerRewardRatio = BigInt.fromI32(10);
workerpool.timestamp = BigInt.zero();
workerpool.usageCount = BigInt.zero();
workerpool.lastUsageTimestamp = BigInt.zero();
workerpool.save();

// First deal
mockViewDeal(pocoAddress, dealId1).returns([
buildDeal(
appAddress,
appOwner,
appPrice,
datasetAddress,
datasetOwner,
datasetPrice,
workerpoolAddress,
workerpoolOwner,
workerpoolPrice,
trust,
category,
tag,
requester,
beneficiary,
callback,
params,
startTime,
botFirst,
botSize1,
workerStake,
schedulerRewardRatio,
sponsor,
),
]);

let mockEvent1 = newTypedMockEventWithParams<OrdersMatched>(
EventParamBuilder.init()
.bytes('dealid', dealId1)
.bytes('appHash', appHash)
.bytes('datasetHash', datasetHash)
.bytes('workerpoolHash', workerpoolHash)
.bytes('requestHash', requestHash)
.build(),
);
mockEvent1.block.timestamp = timestamp1;
mockEvent1.address = pocoAddress;

handleOrdersMatched(mockEvent1);

// Verify first deal usage
assert.fieldEquals('App', appAddress.toHex(), 'usageCount', botSize1.toString());
assert.fieldEquals('App', appAddress.toHex(), 'lastUsageTimestamp', timestamp1.toString());
assert.fieldEquals(
'Workerpool',
workerpoolAddress.toHex(),
'usageCount',
botSize1.toString(),
);
assert.fieldEquals(
'Workerpool',
workerpoolAddress.toHex(),
'lastUsageTimestamp',
timestamp1.toString(),
);

// Second deal
mockViewDeal(pocoAddress, dealId2).returns([
buildDeal(
appAddress,
appOwner,
appPrice,
datasetAddress,
datasetOwner,
datasetPrice,
workerpoolAddress,
workerpoolOwner,
workerpoolPrice,
trust,
category,
tag,
requester,
beneficiary,
callback,
params,
startTime,
botFirst,
botSize2,
workerStake,
schedulerRewardRatio,
sponsor,
),
]);

let mockEvent2 = newTypedMockEventWithParams<OrdersMatched>(
EventParamBuilder.init()
.bytes('dealid', dealId2)
.bytes('appHash', mockBytes32('appHash2'))
.bytes('datasetHash', mockBytes32('datasetHash2'))
.bytes('workerpoolHash', mockBytes32('workerpoolHash2'))
.bytes('requestHash', mockBytes32('requestHash2'))
.build(),
);
mockEvent2.block.timestamp = timestamp2;
mockEvent2.address = pocoAddress;

handleOrdersMatched(mockEvent2);

// Verify accumulated usage
const totalUsage = botSize1.plus(botSize2);
assert.fieldEquals('App', appAddress.toHex(), 'usageCount', totalUsage.toString());
assert.fieldEquals('App', appAddress.toHex(), 'lastUsageTimestamp', timestamp2.toString());
assert.fieldEquals(
'Workerpool',
workerpoolAddress.toHex(),
'usageCount',
totalUsage.toString(),
);
assert.fieldEquals(
'Workerpool',
workerpoolAddress.toHex(),
'lastUsageTimestamp',
timestamp2.toString(),
);
});

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