diff --git a/CHANGELOG.md b/CHANGELOG.md index 604bad6..62151d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/schema.graphql b/schema.graphql index 049951e..2632e0a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -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 @@ -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! diff --git a/src/Modules/IexecPoco.ts b/src/Modules/IexecPoco.ts index e79c04b..e15eeb0 100644 --- a/src/Modules/IexecPoco.ts +++ b/src/Modules/IexecPoco.ts @@ -5,7 +5,6 @@ import { BigInt } from '@graphprotocol/graph-ts'; import { AccurateContribution as AccurateContributionEvent, - DealSponsored as DealSponsoredEvent, FaultyContribution as FaultyContributionEvent, IexecInterfaceToken as IexecInterfaceTokenContract, MatchOrdersCall, @@ -22,7 +21,6 @@ import { import { AccurateContribution, - DealSponsored, FaultyContribution, OrdersMatched, SchedulerNotice, @@ -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(); @@ -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(); -} diff --git a/src/Modules/index.ts b/src/Modules/index.ts index 2ed936f..05689e0 100644 --- a/src/Modules/index.ts +++ b/src/Modules/index.ts @@ -7,7 +7,6 @@ export { handleLock, handleReward, handleSeize, handleTransfer, handleUnlock } f export { handleAccurateContribution, - handleDealSponsored, handleFaultyContribution, handleMatchOrders, handleOrdersMatched, diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 0be4024..cae1b3d 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -69,7 +69,6 @@ dataSources: - Seize - Lock - Unlock - - DealSponsored abis: - name: IexecInterfaceToken file: node_modules/@iexec/poco/artifacts/contracts/IexecInterfaceNative.sol/IexecInterfaceNative.json @@ -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 diff --git a/test/Modules/IexecPoco.test.ts b/test/Modules/IexecPoco.test.ts index 5b56486..27e3778 100644 --- a/test/Modules/IexecPoco.test.ts +++ b/test/Modules/IexecPoco.test.ts @@ -1,39 +1,105 @@ -// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH +// SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH // 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([ - new ethereum.EventParam('deal', ethereum.Value.fromFixedBytes(dealId)), - new ethereum.EventParam('sponsor', ethereum.Value.fromAddress(sponsor)), - ]); + let mockEvent = newTypedMockEventWithParams( + 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(); @@ -41,4 +107,31 @@ describe('IexecPoco', () => { }); }); -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 }; diff --git a/test/utils/EventParamBuilder.ts b/test/utils/EventParamBuilder.ts new file mode 100644 index 0000000..f7992cc --- /dev/null +++ b/test/utils/EventParamBuilder.ts @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts'; + +export class EventParamBuilder { + private params: ethereum.EventParam[] = new Array(); + + 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; + } +} diff --git a/test/utils/mock.ts b/test/utils/mock.ts new file mode 100644 index 0000000..db14363 --- /dev/null +++ b/test/utils/mock.ts @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH +// 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.Value.fromTuple( + // app + changetype([ + ethereum.Value.fromAddress(appAddress), + ethereum.Value.fromAddress(appOwner), + ethereum.Value.fromI32(appPrice.toI32()), + ]), + ), + ethereum.Value.fromTuple( + // dataset + changetype([ + ethereum.Value.fromAddress(datasetAddress), + ethereum.Value.fromAddress(datasetOwner), + ethereum.Value.fromI32(datasetPrice.toI32()), + ]), + ), + ethereum.Value.fromTuple( + // workerpool + changetype([ + 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), + ]), + ); +}