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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## vNext
- Add `sponsor` to `deal`. (#31)
- Update staging deployment hosts. (#29)
- Display coverage in Github PR checks. (#26)
- Add integration test suite. (#21)
Expand Down
9 changes: 1 addition & 8 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ type Deal @entity {
claimedTasksCount: BigInt!
workerStake: BigInt!
schedulerRewardRatio: BigInt!
sponsor: Account!
timestamp: BigInt! # creation
apporder: AppOrder # todo: not available if not broadcasted
datasetorder: DatasetOrder # todo: not available if not broadcasted
Expand Down Expand Up @@ -436,14 +437,6 @@ type OrdersMatched implements DealEvent @entity {
deal: Deal!
}

type DealSponsored implements DealEvent @entity {
id: ID!
transaction: Transaction!
timestamp: BigInt!
deal: Deal!
sponsor: Account!
}

interface TaskEvent {
id: ID!
transaction: Transaction!
Expand Down
16 changes: 1 addition & 15 deletions src/Modules/IexecPoco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { BigInt } from '@graphprotocol/graph-ts';

import {
AccurateContribution as AccurateContributionEvent,
DealSponsored as DealSponsoredEvent,
FaultyContribution as FaultyContributionEvent,
IexecInterfaceToken as IexecInterfaceTokenContract,
MatchOrdersCall,
Expand All @@ -22,7 +21,6 @@ import {

import {
AccurateContribution,
DealSponsored,
FaultyContribution,
OrdersMatched,
SchedulerNotice,
Expand Down Expand Up @@ -156,6 +154,7 @@ export function handleOrdersMatched(event: OrdersMatchedEvent): void {
deal.botSize = viewedDeal.botSize;
deal.workerStake = viewedDeal.workerStake;
deal.schedulerRewardRatio = viewedDeal.schedulerRewardRatio;
deal.sponsor = viewedDeal.sponsor.toHex();
deal.apporder = event.params.appHash.toHex();
deal.datasetorder = event.params.datasetHash.toHex();
deal.workerpoolorder = event.params.workerpoolHash.toHex();
Expand Down Expand Up @@ -423,16 +422,3 @@ export function handleFaultyContribution(event: FaultyContributionEvent): void {
workerAccount.score = faultyContributionEvent.score;
workerAccount.save();
}

export function handleDealSponsored(event: DealSponsoredEvent): void {
let dealSponsoredEvent = DealSponsored.load(createEventID(event));
if (!dealSponsoredEvent) {
dealSponsoredEvent = new DealSponsored(createEventID(event));
}
dealSponsoredEvent.transaction = logTransaction(event).id;
dealSponsoredEvent.transaction = logTransaction(event).id;
dealSponsoredEvent.timestamp = event.block.timestamp;
dealSponsoredEvent.deal = event.params.dealId.toHex();
dealSponsoredEvent.sponsor = event.params.sponsor.toHex();
dealSponsoredEvent.save();
}
1 change: 0 additions & 1 deletion src/Modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export { handleLock, handleReward, handleSeize, handleTransfer, handleUnlock } f

export {
handleAccurateContribution,
handleDealSponsored,
handleFaultyContribution,
handleMatchOrders,
handleOrdersMatched,
Expand Down
3 changes: 0 additions & 3 deletions subgraph.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ dataSources:
- Seize
- Lock
- Unlock
- DealSponsored
abis:
- name: IexecInterfaceToken
file: node_modules/@iexec/poco/artifacts/contracts/IexecInterfaceNative.sol/IexecInterfaceNative.json
Expand Down Expand Up @@ -108,8 +107,6 @@ dataSources:
handler: handleLock
- event: Unlock(address,uint256)
handler: handleUnlock
- event: DealSponsored(bytes32,address)
handler: handleDealSponsored
callHandlers:
- function: matchOrders((address,uint256,uint256,bytes32,address,address,address,bytes32,bytes),(address,uint256,uint256,bytes32,address,address,address,bytes32,bytes),(address,uint256,uint256,bytes32,uint256,uint256,address,address,address,bytes32,bytes),(address,uint256,address,uint256,address,uint256,address,uint256,bytes32,uint256,uint256,address,address,string,bytes32,bytes))
handler: handleMatchOrders
Expand Down
137 changes: 115 additions & 22 deletions test/Modules/IexecPoco.test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,137 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts';
import { BigInt, ethereum } from '@graphprotocol/graph-ts';
import { assert, describe, newTypedMockEventWithParams, test } from 'matchstick-as/assembly/index';
import { DealSponsored } from '../../generated/Core/IexecInterfaceToken';
import { handleDealSponsored } from '../../src/Modules';
import { OrdersMatched } from '../../generated/Core/IexecInterfaceToken';
import { handleOrdersMatched } from '../../src/Modules';
import { toRLC } from '../../src/utils';
import { EventParamBuilder } from '../utils/EventParamBuilder';
import { buildDeal, mockAddress, mockBytes32, mockViewDeal } from '../utils/mock';

describe('IexecPoco', () => {
test('Should handle DealSponsored', () => {
// Define mock parameters
const dealId = Bytes.fromHexString(
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
);
const sponsor = Address.fromString('0xabcdef1234567890abcdef1234567890abcdef12');
const timestamp = BigInt.fromI32(123456789);
const pocoAddress = mockAddress('pocoAddress');
const dealId = mockBytes32('dealId');
const appHash = mockBytes32('appHash');
const datasetHash = mockBytes32('datasetHash');
const workerpoolHash = mockBytes32('workerpoolHash');
const requestHash = mockBytes32('requestHash');
const timestamp = BigInt.fromI32(123456789);
const appAddress = mockAddress('appAddress');
const appOwner = mockAddress('appOwner');
const appPrice = BigInt.fromI32(1);
const datasetAddress = mockAddress('datasetAddress');
const datasetOwner = mockAddress('datasetOwner');
const datasetPrice = BigInt.fromI32(2);
const workerpoolAddress = mockAddress('workerpoolAddress');
const workerpoolOwner = mockAddress('workerpoolOwner');
const workerpoolPrice = BigInt.fromI32(3);
const trust = BigInt.fromI32(4);
const category = BigInt.fromI32(5);
const tag = mockBytes32('tag');
const requester = mockAddress('requester');
const beneficiary = mockAddress('beneficiary');
const callback = mockAddress('callback');
const params = 'params';
const startTime = BigInt.fromI32(6);
const botFirst = BigInt.fromI32(7);
const botSize = BigInt.fromI32(8);
const workerStake = BigInt.fromI32(9);
const schedulerRewardRatio = BigInt.fromI32(10);
const sponsor = mockAddress('sponsor');

describe('IexecPoco', () => {
test('Should handle OrdersMatched', () => {
mockViewDeal(pocoAddress, dealId).returns([buildDefaultDeal()]);
// Create the mock event
let mockEvent = newTypedMockEventWithParams<DealSponsored>([
new ethereum.EventParam('deal', ethereum.Value.fromFixedBytes(dealId)),
new ethereum.EventParam('sponsor', ethereum.Value.fromAddress(sponsor)),
]);
let mockEvent = newTypedMockEventWithParams<OrdersMatched>(
EventParamBuilder.init()
.bytes('dealid', dealId)
.bytes('appHash', appHash)
.bytes('datasetHash', datasetHash)
.bytes('workerpoolHash', workerpoolHash)
.bytes('requestHash', requestHash)
.build(),
);
mockEvent.block.timestamp = timestamp;
mockEvent.address = pocoAddress;

// Call the handler
handleDealSponsored(mockEvent);
handleOrdersMatched(mockEvent);

// Assert that the DealSponsored entity was created and has correct fields
// Assert that the OrdersMatched entity was created and has correct fields
const entityId = mockEvent.block.number
.toString()
.concat('-')
.concat(mockEvent.logIndex.toString());

assert.fieldEquals('DealSponsored', entityId, 'deal', dealId.toHex());
assert.fieldEquals('DealSponsored', entityId, 'sponsor', sponsor.toHex());
assert.fieldEquals('DealSponsored', entityId, 'timestamp', timestamp.toString());
assert.fieldEquals('OrdersMatched', entityId, 'deal', dealId.toHex());
assert.fieldEquals('OrdersMatched', entityId, 'timestamp', timestamp.toString());
// Check deal
assert.fieldEquals('Deal', dealId.toHex(), 'app', appAddress.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'appOwner', appOwner.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'appPrice', toRLC(appPrice).toString());
assert.fieldEquals('Deal', dealId.toHex(), 'dataset', datasetAddress.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'datasetOwner', datasetOwner.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'datasetPrice', toRLC(datasetPrice).toString());
assert.fieldEquals('Deal', dealId.toHex(), 'workerpool', workerpoolAddress.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'workerpoolOwner', workerpoolOwner.toHex());
assert.fieldEquals(
'Deal',
dealId.toHex(),
'workerpoolPrice',
toRLC(workerpoolPrice).toString(),
);
assert.fieldEquals('Deal', dealId.toHex(), 'trust', trust.toString());
assert.fieldEquals('Deal', dealId.toHex(), 'category', category.toString());
assert.fieldEquals('Deal', dealId.toHex(), 'tag', tag.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'requester', requester.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'beneficiary', beneficiary.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'callback', callback.toHex());
assert.fieldEquals('Deal', dealId.toHex(), 'params', params);
assert.fieldEquals('Deal', dealId.toHex(), 'startTime', startTime.toString());
assert.fieldEquals('Deal', dealId.toHex(), 'botFirst', botFirst.toString());
assert.fieldEquals('Deal', dealId.toHex(), 'botSize', botSize.toString());
assert.fieldEquals('Deal', dealId.toHex(), 'workerStake', workerStake.toString());
assert.fieldEquals(
'Deal',
dealId.toHex(),
'schedulerRewardRatio',
schedulerRewardRatio.toString(),
);
assert.fieldEquals('Deal', dealId.toHex(), 'sponsor', sponsor.toHex());
// TODO: Check other saved entities

// Assert that a transaction was logged (if applicable)
const transactionId = mockEvent.transaction.hash.toHex();
assert.fieldEquals('Transaction', transactionId, 'id', transactionId);
});
});

export { handleDealSponsored };
function buildDefaultDeal(): ethereum.Value {
return buildDeal(
appAddress,
appOwner,
appPrice,
datasetAddress,
datasetOwner,
datasetPrice,
workerpoolAddress,
workerpoolOwner,
workerpoolPrice,
trust,
category,
tag,
requester,
beneficiary,
callback,
params,
startTime,
botFirst,
botSize,
workerStake,
schedulerRewardRatio,
sponsor,
);
}

export { OrdersMatched };
36 changes: 36 additions & 0 deletions test/utils/EventParamBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts';

export class EventParamBuilder {
private params: ethereum.EventParam[] = new Array<ethereum.EventParam>();

static init(): EventParamBuilder {
return new EventParamBuilder();
}

address(key: string, value: Address): EventParamBuilder {
this.params.push(new ethereum.EventParam(key, ethereum.Value.fromAddress(value)));
return this;
}

bytes(key: string, value: Bytes): EventParamBuilder {
this.params.push(new ethereum.EventParam(key, ethereum.Value.fromBytes(value)));
return this;
}

bigInt(key: string, value: BigInt): EventParamBuilder {
this.params.push(new ethereum.EventParam(key, ethereum.Value.fromUnsignedBigInt(value)));
return this;
}

string(key: string, value: string): EventParamBuilder {
this.params.push(new ethereum.EventParam(key, ethereum.Value.fromString(value)));
return this;
}

build(): ethereum.EventParam[] {
return this.params;
}
}
88 changes: 88 additions & 0 deletions test/utils/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

import { Address, BigInt, ByteArray, Bytes, crypto, ethereum } from '@graphprotocol/graph-ts';
import { createMockedFunction, MockedFunction } from 'matchstick-as';

export function mockBytes32(input: string): Bytes {
return Bytes.fromHexString(crypto.keccak256(ByteArray.fromUTF8(input)).toHex());
}

export function mockAddress(input: string): Address {
return Address.fromString(mockBytes32(input).toHex().slice(0, 42));
}

export function mockViewDeal(pocoProxyAddress: Address, dealId: Bytes): MockedFunction {
return createMockedFunction(
pocoProxyAddress,
'viewDeal',
'viewDeal(bytes32):(((address,address,uint256),(address,address,uint256),(address,address,uint256),uint256,uint256,bytes32,address,address,address,string,uint256,uint256,uint256,uint256,uint256,address))',
).withArgs([ethereum.Value.fromFixedBytes(dealId)]);
}

export function buildDeal(
appAddress: Address,
appOwner: Address,
appPrice: BigInt,
datasetAddress: Address,
datasetOwner: Address,
datasetPrice: BigInt,
workerpoolAddress: Address,
workerpoolOwner: Address,
workerpoolPrice: BigInt,
trust: BigInt,
category: BigInt,
tag: Bytes,
requester: Address,
beneficiary: Address,
callback: Address,
params: string,
startTime: BigInt,
botFirst: BigInt,
botSize: BigInt,
workerStake: BigInt,
schedulerRewardRatio: BigInt,
sponsor: Address,
): ethereum.Value {
return ethereum.Value.fromTuple(
changetype<ethereum.Tuple>([
ethereum.Value.fromTuple(
// app
changetype<ethereum.Tuple>([
ethereum.Value.fromAddress(appAddress),
ethereum.Value.fromAddress(appOwner),
ethereum.Value.fromI32(appPrice.toI32()),
]),
),
ethereum.Value.fromTuple(
// dataset
changetype<ethereum.Tuple>([
ethereum.Value.fromAddress(datasetAddress),
ethereum.Value.fromAddress(datasetOwner),
ethereum.Value.fromI32(datasetPrice.toI32()),
]),
),
ethereum.Value.fromTuple(
// workerpool
changetype<ethereum.Tuple>([
ethereum.Value.fromAddress(workerpoolAddress),
ethereum.Value.fromAddress(workerpoolOwner),
ethereum.Value.fromI32(workerpoolPrice.toI32()),
]),
),
ethereum.Value.fromI32(trust.toI32()),
ethereum.Value.fromI32(category.toI32()),
ethereum.Value.fromFixedBytes(tag),
ethereum.Value.fromAddress(requester),
ethereum.Value.fromAddress(beneficiary),
ethereum.Value.fromAddress(callback),
ethereum.Value.fromString(params),
ethereum.Value.fromI32(startTime.toI32()),
ethereum.Value.fromI32(botFirst.toI32()),
ethereum.Value.fromI32(botSize.toI32()),
ethereum.Value.fromI32(workerStake.toI32()),
ethereum.Value.fromI32(schedulerRewardRatio.toI32()),
ethereum.Value.fromAddress(sponsor),
]),
);
}