Skip to content
Draft
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
6 changes: 6 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type IndexingAgreement @entity(immutable: false) {
state: AgreementState!
"Timestamp when the agreement was accepted"
acceptedAt: BigInt!
"Transaction hash of the SubgraphService.acceptIndexingAgreement call. Auditable link from the indexed agreement back to the on-chain accept (typically a multicall(startService+acceptIndexingAgreement)). 32-byte zero sentinel if never accepted (entity created from a stray Updated/Canceled event)."
acceptedAtTx: Bytes!
"Timestamp of last collection"
lastCollectionAt: BigInt!
"Timestamp when the agreement ends"
Expand All @@ -42,6 +44,8 @@ type IndexingAgreement @entity(immutable: false) {
lastUpdatedAt: BigInt!
"Timestamp when agreement was canceled (0 if not canceled)"
canceledAt: BigInt!
"Transaction hash of the SubgraphService.cancelIndexingAgreement* call (32-byte zero if not canceled). Mirrors canceledAt/canceledBy for full auditability of the cancel event."
canceledAtTx: Bytes!
"Address that initiated the cancel (zero address if not canceled). Taken from SubgraphService.IndexingAgreementCanceled.canceledOnBehalfOf so operator-initiated cancels are captured correctly."
canceledBy: Bytes!
"Total tokens collected over lifetime"
Expand All @@ -62,6 +66,8 @@ type IndexingFeeCollection @entity(immutable: true) {
poiBlockNumber: BigInt!
blockNumber: BigInt!
blockTimestamp: BigInt!
"Transaction hash of the SubgraphService.collect call that emitted this fee collection. Auditable per-payment link."
transactionHash: Bytes!
}

type IndexerDeploymentLatest @entity(immutable: false) {
Expand Down
7 changes: 7 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts'
import { IndexingAgreement } from '../generated/schema'

export const BIGINT_ZERO = BigInt.fromI32(0)
// 32-byte zero — same rationale as the canceledBy = Address.zero() pattern
// below. Bytes.empty() serializes with unpredictable padding on non-nullable
// fields; a fixed-length sentinel gives consumers a deterministic value to
// check against when no on-chain tx exists yet.
export const BYTES32_ZERO = Bytes.fromHexString('0x' + '00'.repeat(32)) as Bytes

export function createOrLoadIndexingAgreement(agreementId: Bytes): IndexingAgreement {
let agreement = IndexingAgreement.load(agreementId)
Expand All @@ -13,6 +18,7 @@ export function createOrLoadIndexingAgreement(agreementId: Bytes): IndexingAgree
agreement.subgraphDeploymentId = Bytes.empty()
agreement.state = 'NotAccepted'
agreement.acceptedAt = BIGINT_ZERO
agreement.acceptedAtTx = BYTES32_ZERO
agreement.lastCollectionAt = BIGINT_ZERO
agreement.endsAt = BIGINT_ZERO
agreement.maxInitialTokens = BIGINT_ZERO
Expand All @@ -23,6 +29,7 @@ export function createOrLoadIndexingAgreement(agreementId: Bytes): IndexingAgree
agreement.maxSecondsPerCollection = 0
agreement.lastUpdatedAt = BIGINT_ZERO
agreement.canceledAt = BIGINT_ZERO
agreement.canceledAtTx = BYTES32_ZERO
// Default to 20-byte zero address rather than Bytes.empty(). Graph-node
// serializes empty Bytes on non-nullable fields with unpredictable
// padding (observed as "0x00000000" in practice), which breaks strict
Expand Down
3 changes: 3 additions & 0 deletions src/subgraphService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function handleIndexingAgreementAccepted(event: AcceptedEvent): void {
let agreement = createOrLoadIndexingAgreement(event.params.agreementId)
agreement.allocationId = event.params.allocationId
agreement.subgraphDeploymentId = event.params.subgraphDeploymentId
agreement.acceptedAtTx = event.transaction.hash

let decoded = ethereum.decode('(uint256,uint256)', event.params.versionTerms)
if (decoded != null) {
Expand All @@ -31,6 +32,7 @@ export function handleIndexingAgreementCanceled(event: CanceledEvent): void {
// directly. Dipper's chain_listener compares this to its own signer
// address to decide CanceledByRequester vs CanceledByIndexer.
agreement.canceledBy = event.params.canceledOnBehalfOf
agreement.canceledAtTx = event.transaction.hash
agreement.lastStateChangeBlock = event.block.number
agreement.save()
}
Expand Down Expand Up @@ -61,6 +63,7 @@ export function handleIndexingFeesCollectedV1(event: FeesCollectedEvent): void {
collection.poiBlockNumber = event.params.poiBlockNumber
collection.blockNumber = event.block.number
collection.blockTimestamp = event.block.timestamp
collection.transactionHash = event.transaction.hash
collection.save()

let compositeId =
Expand Down
29 changes: 29 additions & 0 deletions tests/subgraphService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ describe('handleIndexingAgreementAccepted', () => {
versionTerms,
)
event.block.number = BigInt.fromI32(100)
let acceptTxHash = Bytes.fromHexString('0x' + 'aa'.repeat(32)) as Bytes
event.transaction.hash = acceptTxHash
handleIndexingAgreementAccepted(event)

assert.entityCount('IndexingAgreement', 1)
Expand All @@ -186,6 +188,12 @@ describe('handleIndexingAgreementAccepted', () => {
'allocationId',
allocationId.toHexString(),
)
assert.fieldEquals(
'IndexingAgreement',
agreementId.toHexString(),
'acceptedAtTx',
acceptTxHash.toHexString(),
)
assert.fieldEquals(
'IndexingAgreement',
agreementId.toHexString(),
Expand Down Expand Up @@ -225,6 +233,8 @@ describe('handleIndexingAgreementCanceled', () => {

let event = createCanceledEvent(indexer, payer, agreementId, operator)
event.block.number = BigInt.fromI32(200)
let cancelTxHash = Bytes.fromHexString('0x' + 'bb'.repeat(32)) as Bytes
event.transaction.hash = cancelTxHash
handleIndexingAgreementCanceled(event)

assert.fieldEquals(
Expand All @@ -233,6 +243,12 @@ describe('handleIndexingAgreementCanceled', () => {
'canceledBy',
operator.toHexString(),
)
assert.fieldEquals(
'IndexingAgreement',
agreementId.toHexString(),
'canceledAtTx',
cancelTxHash.toHexString(),
)
assert.fieldEquals(
'IndexingAgreement',
agreementId.toHexString(),
Expand Down Expand Up @@ -335,13 +351,26 @@ describe('handleIndexingFeesCollectedV1', () => {
poiBlockNumber,
metadata,
)
let collectTxHash = Bytes.fromHexString('0x' + 'cd'.repeat(32)) as Bytes
event.transaction.hash = collectTxHash
event.logIndex = BigInt.fromI32(0)
handleIndexingFeesCollectedV1(event)

let compositeId = indexer.toHexString() + '-' + subgraphDeploymentId.toHexString()
assert.entityCount('IndexerDeploymentLatest', 1)
assert.fieldEquals('IndexerDeploymentLatest', compositeId, 'entities', '5000')
assert.fieldEquals('IndexerDeploymentLatest', compositeId, 'tokensCollected', '1000000')
assert.fieldEquals('IndexerDeploymentLatest', compositeId, 'indexer', indexer.toHexString())

// IndexingFeeCollection id is event.transaction.hash.concatI32(logIndex).
let collectionId = collectTxHash.concatI32(0).toHexString()
assert.entityCount('IndexingFeeCollection', 1)
assert.fieldEquals(
'IndexingFeeCollection',
collectionId,
'transactionHash',
collectTxHash.toHexString(),
)
})

test('second collection updates IndexerDeploymentLatest', () => {
Expand Down