From 23c17dacb7ffc6bf7b877f494f5a303357e03bd4 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Fri, 25 Jul 2025 23:37:10 +0000 Subject: [PATCH 1/7] fix: horizon tests --- packages/indexer-cli/src/__tests__/util.ts | 2 + .../src/allocations/graph-tally-collector.ts | 104 ++++--- .../allocations/horizon-escrow-accounts.ts | 17 +- .../src/indexer-management/allocations.ts | 260 ++++++++++-------- .../src/indexer-management/types.ts | 2 +- packages/indexer-common/src/network.ts | 1 - .../indexer-common/src/query-fees/models.ts | 39 +-- 7 files changed, 244 insertions(+), 181 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/util.ts b/packages/indexer-cli/src/__tests__/util.ts index bf702416a..bf8f1b9f3 100644 --- a/packages/indexer-cli/src/__tests__/util.ts +++ b/packages/indexer-cli/src/__tests__/util.ts @@ -279,6 +279,7 @@ export const seedActions = async () => { source: 'test', reason: 'test', protocolNetwork: 'eip155:421614', + isLegacy: false, }) await models.Action.create({ id: 2, @@ -288,6 +289,7 @@ export const seedActions = async () => { source: 'test', reason: 'test', protocolNetwork: 'eip155:421614', + isLegacy: false, }) } catch (e) { logger.error('Failed to seed ', { error: e }) diff --git a/packages/indexer-common/src/allocations/graph-tally-collector.ts b/packages/indexer-common/src/allocations/graph-tally-collector.ts index e502dfea6..1108b10a3 100644 --- a/packages/indexer-common/src/allocations/graph-tally-collector.ts +++ b/packages/indexer-common/src/allocations/graph-tally-collector.ts @@ -112,7 +112,7 @@ export class GraphTallyCollector { declare indexerAddress: Address // eslint-disable-next-line @typescript-eslint/no-empty-function -- Private constructor to prevent direct instantiation - private constructor() { } + private constructor() {} public static create({ logger, @@ -163,8 +163,8 @@ export class GraphTallyCollector { ravRedemptionThreshold: formatGRT(this.ravRedemptionThreshold), belowThresholdCount: signedRavs.belowThreshold.length, totalValueGRT, - allocations: signedRavs.belowThreshold.map( - (signedRav) => dataSlice(signedRav.rav.rav.collectionId, 12).toString(), + allocations: signedRavs.belowThreshold.map((signedRav) => + dataSlice(signedRav.rav.rav.collectionId, 12).toString(), ), }) } @@ -205,7 +205,9 @@ export class GraphTallyCollector { return { rav: signedRav, allocation: allocations.find( - (a) => a.id === toAddress(dataSlice(signedRav.rav.collectionId, 12).toString()), + (a) => + a.id === + toAddress(dataSlice(signedRav.rav.collectionId, 12).toString()), ), payer: rav.payer, } @@ -228,7 +230,7 @@ export class GraphTallyCollector { // eslint-disable-next-line @typescript-eslint/no-explicit-any const returnedAllocations: any[] = [] - for (; ;) { + for (;;) { const result = await this.networkSubgraph.query( gql` query allocations( @@ -301,15 +303,24 @@ export class GraphTallyCollector { private getSignedRAVsEventual( pendingRAVs: Eventual, ): Eventual { - return pendingRAVs.tryMap( async (pendingRAVs) => { - const escrowAccounts = await getEscrowAccounts(this.networkSubgraph, this.indexerAddress, this.contracts.GraphTallyCollector.target.toString()) + const escrowAccounts = await getEscrowAccounts( + this.networkSubgraph, + this.indexerAddress, + this.contracts.GraphTallyCollector.target.toString(), + ) return await pReduce( pendingRAVs, async (results, rav) => { - const tokensCollected = escrowAccounts.getTokensCollectedForReceiver(rav.payer, rav.rav.rav.collectionId) - if ((BigInt(rav.rav.rav.valueAggregate) - tokensCollected) < this.ravRedemptionThreshold) { + const tokensCollected = escrowAccounts.getTokensCollectedForReceiver( + rav.payer, + rav.rav.rav.collectionId, + ) + if ( + BigInt(rav.rav.rav.valueAggregate) - tokensCollected < + this.ravRedemptionThreshold + ) { results.belowThreshold.push(rav) } else { results.eligible.push(rav) @@ -416,14 +427,16 @@ export class GraphTallyCollector { const transactions: GraphTallyTransaction[] = [] const unfinalizedRavsAllocationIds = [ - ...new Set(ravs.map((value) => toAddress(value.collectionId.slice(12)).toLowerCase())), + ...new Set( + ravs.map((value) => toAddress(value.collectionId.slice(12)).toLowerCase()), + ), ] const payerAddresses = [ ...new Set(ravs.map((value) => toAddress(value.payer).toLowerCase())), ] - for (; ;) { + for (;;) { let block: { hash: string } | undefined = undefined if (meta?.block?.hash) { block = { @@ -510,17 +523,17 @@ export class GraphTallyCollector { UPDATE tap_horizon_ravs SET redeemed_at = NULL WHERE (collection_id::char(64), payer::char(40)) IN (VALUES ${ravsNotRedeemed - .map( - (rav) => - `('${rav.collectionId - .toString() - .toLowerCase() - .replace('0x', '')}'::char(64), '${rav.payer + .map( + (rav) => + `('${rav.collectionId + .toString() + .toLowerCase() + .replace('0x', '')}'::char(64), '${rav.payer .toString() .toLowerCase() .replace('0x', '')}'::char(40))`, - ) - .join(', ')}) + ) + .join(', ')}) AND redeemed_at < to_timestamp(${blockTimestampSecs}) ` @@ -558,17 +571,27 @@ export class GraphTallyCollector { signedRavs, }) - const escrowAccounts = await getEscrowAccounts(this.networkSubgraph, this.indexerAddress, this.contracts.GraphTallyCollector.target.toString()) + const escrowAccounts = await getEscrowAccounts( + this.networkSubgraph, + this.indexerAddress, + this.contracts.GraphTallyCollector.target.toString(), + ) // Redeem RAV one-by-one as no plual version available - const tokensCollectedPerAllocation: { allocationId: string, tokensCollected: bigint }[] = []; + const tokensCollectedPerAllocation: { + allocationId: string + tokensCollected: bigint + }[] = [] for (const { rav: signedRav, allocation, payer } of signedRavs) { const { rav } = signedRav // verify escrow balances const ravValue = BigInt(rav.valueAggregate.toString()) - const tokensAlreadyCollected = escrowAccounts.getTokensCollectedForReceiver(payer, rav.collectionId) + const tokensAlreadyCollected = escrowAccounts.getTokensCollectedForReceiver( + payer, + rav.collectionId, + ) const payerBalance = escrowAccounts.getBalanceForPayer(payer) // In horizon the RAV value is monotonically increasing. To calculate the actual outstanding amount we need to subtract the tokens already collected. @@ -597,7 +620,10 @@ export class GraphTallyCollector { if (!actualTokensCollected) { throw new Error(`Failed to redeem RAV v2: no tokens collected`) } - tokensCollectedPerAllocation.push({ allocationId: allocation.id, tokensCollected: actualTokensCollected }) + tokensCollectedPerAllocation.push({ + allocationId: allocation.id, + tokensCollected: actualTokensCollected, + }) escrowAccounts.updateBalances(payer, rav.collectionId, actualTokensCollected) } catch (err) { this.metrics.ravRedeemsFailed.inc({ collection: rav.collectionId }) @@ -650,7 +676,12 @@ export class GraphTallyCollector { // Submit the signed RAV on chain const txReceipt = await this.transactionManager.executeTransaction( - () => this.contracts.SubgraphService.collect.estimateGas(rav.serviceProvider, PaymentTypes.QueryFee, encodedData), + () => + this.contracts.SubgraphService.collect.estimateGas( + rav.serviceProvider, + PaymentTypes.QueryFee, + encodedData, + ), (gasLimit) => this.contracts.SubgraphService.collect(rav.serviceProvider, 0, encodedData, { gasLimit, @@ -668,14 +699,16 @@ export class GraphTallyCollector { const contractInterface = this.contracts.GraphTallyCollector.interface const event = contractInterface.getEvent('PaymentCollected') - const log = txReceipt.logs.find(log => log.topics[0] === event.topicHash); - if (!log) throw new Error('PaymentCollected event not found!'); + const log = txReceipt.logs.find((log) => log.topics[0] === event.topicHash) + if (!log) throw new Error('PaymentCollected event not found!') - const decoded = contractInterface.decodeEventLog(event, log.data, log.topics); + const decoded = contractInterface.decodeEventLog(event, log.data, log.topics) if (!decoded.tokens) { - throw new Error(`Actual value collected not found for collection ${rav.collectionId}`) + throw new Error( + `Actual value collected not found for collection ${rav.collectionId}`, + ) } - const actualTokensCollected = BigInt(decoded.tokens); + const actualTokensCollected = BigInt(decoded.tokens) this.metrics.ravCollectedFees.set( { collection: rav.collectionId }, @@ -696,7 +729,7 @@ export class GraphTallyCollector { ) } - return actualTokensCollected; + return actualTokensCollected } private async markRavAsRedeemed( @@ -710,13 +743,10 @@ export class GraphTallyCollector { UPDATE tap_horizon_ravs SET redeemed_at = ${timestamp ? `to_timestamp(${timestamp})` : 'NOW()'} WHERE collection_id = '${collectionId - .toString() - .toLowerCase() - .replace('0x', '')}' - AND payer = '${payer - .toString() - .toLowerCase() - .replace('0x', '')}' + .toString() + .toLowerCase() + .replace('0x', '')}' + AND payer = '${payer.toString().toLowerCase().replace('0x', '')}' ` await this.models.receiptAggregateVouchersV2.sequelize?.query(query) diff --git a/packages/indexer-common/src/allocations/horizon-escrow-accounts.ts b/packages/indexer-common/src/allocations/horizon-escrow-accounts.ts index 6607364a9..4a7216612 100644 --- a/packages/indexer-common/src/allocations/horizon-escrow-accounts.ts +++ b/packages/indexer-common/src/allocations/horizon-escrow-accounts.ts @@ -7,14 +7,14 @@ export type PaymentsEscrowAccountResponse = { payer: { id: string } - }[], + }[] // Not a typo just how graph-client pluralizes the field name graphTallyTokensCollecteds: { tokens: string collectionId: string payer: { id: string - }, + } }[] } @@ -22,7 +22,10 @@ export class PaymentsEscrowAccounts { private payersBalances: Map private receiversTokensCollected: Map - constructor(payersBalances: Map, receiversTokensCollected: Map) { + constructor( + payersBalances: Map, + receiversTokensCollected: Map, + ) { this.payersBalances = payersBalances this.receiversTokensCollected = receiversTokensCollected } @@ -38,7 +41,9 @@ export class PaymentsEscrowAccounts { getTokensCollectedForReceiver(payer: string, collectionId: string): bigint { const balance = this.receiversTokensCollected.get(`${payer}-${collectionId}`) if (balance === undefined) { - throw new Error(`No tokens collected found for payer: ${payer} and collectionId: ${collectionId}`) + throw new Error( + `No tokens collected found for payer: ${payer} and collectionId: ${collectionId}`, + ) } return balance } @@ -81,7 +86,9 @@ export const getEscrowAccounts = async ( const result = await subgraph.query( gql` query PaymentsEscrowAccountQuery($indexer: ID!, $collector: String!) { - paymentsEscrowAccounts(where: { receiver_: { id: $indexer }, collector: $collector }) { + paymentsEscrowAccounts( + where: { receiver_: { id: $indexer }, collector: $collector } + ) { balance payer { id diff --git a/packages/indexer-common/src/indexer-management/allocations.ts b/packages/indexer-common/src/indexer-management/allocations.ts index e85c04bfd..144f63b45 100644 --- a/packages/indexer-common/src/indexer-management/allocations.ts +++ b/packages/indexer-common/src/indexer-management/allocations.ts @@ -126,7 +126,7 @@ export class AllocationManager { private models: IndexerManagementModels, private graphNode: GraphNode, private network: Network, - ) { } + ) {} async executeBatch( actions: Action[], @@ -156,8 +156,11 @@ export class AllocationManager { await onFinishedDeploying(validatedActions) // Populated transaction result for each action - const populatedActionTransactionsResults = await this.prepareTransactions(validatedActions) - logger.trace('populatedActionTransactionsResults', { populatedActionTransactionsResults }) + const populatedActionTransactionsResults = + await this.prepareTransactions(validatedActions) + logger.trace('populatedActionTransactionsResults', { + populatedActionTransactionsResults, + }) // Flat list of valid prepared transactions const preparedTransactions: ActionTransactionRequest[] = [] @@ -183,16 +186,14 @@ export class AllocationManager { preparedTransactions: preparedTransactions, }) - // We need to process both staking contract and subgraph service transactions separately + // We need to process both staking contract and subgraph service transactions separately // as they cannot be multicalled // Realistically, the only scenario where a batch would have both is shortly after the horizon upgrade const stakingTransactions = preparedTransactions.filter( - (tx: TransactionRequest) => - tx.to === this.network.contracts.HorizonStaking.target, + (tx: TransactionRequest) => tx.to === this.network.contracts.HorizonStaking.target, ) const subgraphServiceTransactions = preparedTransactions.filter( - (tx: TransactionRequest) => - tx.to === this.network.contracts.SubgraphService.target, + (tx: TransactionRequest) => tx.to === this.network.contracts.SubgraphService.target, ) // -- STAKING CONTRACT -- @@ -209,20 +210,21 @@ export class AllocationManager { if (callDataStakingContract.length > 0) { try { - const stakingTransactionResult = await this.network.transactionManager.executeTransaction( - async () => - this.network.contracts.HorizonStaking.multicall.estimateGas( - callDataStakingContract, - ), - async (gasLimit) => - this.network.contracts.HorizonStaking.multicall(callDataStakingContract, { - gasLimit, + const stakingTransactionResult = + await this.network.transactionManager.executeTransaction( + async () => + this.network.contracts.HorizonStaking.multicall.estimateGas( + callDataStakingContract, + ), + async (gasLimit) => + this.network.contracts.HorizonStaking.multicall(callDataStakingContract, { + gasLimit, + }), + this.logger.child({ + actions: `${JSON.stringify(validatedActions.map((action) => action.id))}`, + function: 'staking.multicall', }), - this.logger.child({ - actions: `${JSON.stringify(validatedActions.map((action) => action.id))}`, - function: 'staking.multicall', - }), - ) + ) // Mark all actions with staking contract transactions as successful for (const transaction of stakingTransactions) { @@ -252,13 +254,17 @@ export class AllocationManager { // -- SUBGRAPH SERVICE -- const callDataSubgraphService = subgraphServiceTransactions - // Reallocate of a legacy allocation during the transition period can result in - // a staking and subgraph service transaction in the same batch. If the staking tx failed we + // Reallocate of a legacy allocation during the transition period can result in + // a staking and subgraph service transaction in the same batch. If the staking tx failed we // should not execute the subgraph service tx. .filter((tx: ActionTransactionRequest) => { - const actionStakingTransaction = actionResults - .find((result) => result.actionID === tx.actionID) - return actionStakingTransaction === undefined || actionStakingTransaction.success === true + const actionStakingTransaction = actionResults.find( + (result) => result.actionID === tx.actionID, + ) + return ( + actionStakingTransaction === undefined || + actionStakingTransaction.success === true + ) }) .filter((tx: TransactionRequest) => !!tx.data) .map((tx) => tx.data as string) @@ -271,20 +277,21 @@ export class AllocationManager { if (callDataSubgraphService.length > 0) { try { - const subgraphServiceTransactionResult = await this.network.transactionManager.executeTransaction( - async () => - this.network.contracts.SubgraphService.multicall.estimateGas( - callDataSubgraphService, - ), - async (gasLimit) => - this.network.contracts.SubgraphService.multicall(callDataSubgraphService, { - gasLimit, + const subgraphServiceTransactionResult = + await this.network.transactionManager.executeTransaction( + async () => + this.network.contracts.SubgraphService.multicall.estimateGas( + callDataSubgraphService, + ), + async (gasLimit) => + this.network.contracts.SubgraphService.multicall(callDataSubgraphService, { + gasLimit, + }), + this.logger.child({ + actions: `${JSON.stringify(validatedActions.map((action) => action.id))}`, + function: 'subgraphService.multicall', }), - this.logger.child({ - actions: `${JSON.stringify(validatedActions.map((action) => action.id))}`, - function: 'subgraphService.multicall', - }), - ) + ) // Mark all actions with subgraph service transactions as successful for (const transaction of subgraphServiceTransactions) { @@ -336,7 +343,6 @@ export class AllocationManager { return pMap( transactionResults, async (transactionResult: ExecuteTransactionResult) => { - const action = actions.find((action) => action.id === transactionResult.actionID) if (action === undefined) { this.logger.error('No action found for transaction result', { @@ -346,12 +352,21 @@ export class AllocationManager { } if (isActionFailure(transactionResult.result)) { - logger.debug('Execute batch transaction failed', { actionBatchResult: transactionResult, reason: transactionResult.result.failureReason }) + logger.debug('Execute batch transaction failed', { + actionBatchResult: transactionResult, + reason: transactionResult.result.failureReason, + }) return transactionResult.result } - if (transactionResult.result === "paused" || transactionResult.result === "unauthorized") { - logger.debug('Execute batch transaction failed', { actionBatchResult: transactionResult, reason: transactionResult.result }) + if ( + transactionResult.result === 'paused' || + transactionResult.result === 'unauthorized' + ) { + logger.debug('Execute batch transaction failed', { + actionBatchResult: transactionResult, + reason: transactionResult.result, + }) return { actionID: transactionResult.actionID, transactionID: undefined, @@ -654,17 +669,17 @@ export class AllocationManager { const proof = isHorizon ? await horizonAllocationIdProof( - allocationSigner, - Number(this.network.specification.networkIdentifier.split(':')[1]), - this.network.specification.indexerOptions.address, - allocationId, - this.network.contracts.SubgraphService.target.toString(), - ) + allocationSigner, + Number(this.network.specification.networkIdentifier.split(':')[1]), + this.network.specification.indexerOptions.address, + allocationId, + this.network.contracts.SubgraphService.target.toString(), + ) : await legacyAllocationIdProof( - allocationSigner, - this.network.specification.indexerOptions.address, - allocationId, - ) + allocationSigner, + this.network.specification.indexerOptions.address, + allocationId, + ) logger.debug('Successfully generated allocation ID proof', { allocationIDProof: proof, @@ -698,28 +713,29 @@ export class AllocationManager { throw indexerError( IndexerErrorCode.IE062, - `Allocation not created. ${receipt === 'paused' ? 'Network paused' : 'Operator not authorized' + `Allocation not created. ${ + receipt === 'paused' ? 'Network paused' : 'Operator not authorized' }`, ) } const createAllocationEventLogs = isLegacy ? this.network.transactionManager.findEvent( - 'AllocationCreated', - this.network.contracts.LegacyStaking.interface, - 'subgraphDeploymentID', - subgraphDeployment.bytes32, - receipt, - this.logger, - ) + 'AllocationCreated', + this.network.contracts.LegacyStaking.interface, + 'subgraphDeploymentID', + subgraphDeployment.bytes32, + receipt, + this.logger, + ) : this.network.transactionManager.findEvent( - 'AllocationCreated', - this.network.contracts.SubgraphService.interface, - 'indexer', - this.network.specification.indexerOptions.address, - receipt, - logger, - ) + 'AllocationCreated', + this.network.contracts.SubgraphService.interface, + 'indexer', + this.network.specification.indexerOptions.address, + receipt, + logger, + ) if (!createAllocationEventLogs) { throw indexerError(IndexerErrorCode.IE014, `Allocation was never mined`) @@ -894,21 +910,21 @@ export class AllocationManager { const closeAllocationEventLogs = isLegacy ? this.network.transactionManager.findEvent( - 'AllocationClosed', - this.network.contracts.LegacyStaking.interface, - 'allocationID', - allocationID, - receipt, - this.logger, - ) + 'AllocationClosed', + this.network.contracts.LegacyStaking.interface, + 'allocationID', + allocationID, + receipt, + this.logger, + ) : this.network.transactionManager.findEvent( - 'AllocationClosed', - this.network.contracts.SubgraphService.interface, - 'allocationId', - allocationID, - receipt, - this.logger, - ) + 'AllocationClosed', + this.network.contracts.SubgraphService.interface, + 'allocationId', + allocationID, + receipt, + this.logger, + ) if (!closeAllocationEventLogs) { throw indexerError( @@ -919,21 +935,21 @@ export class AllocationManager { const rewardsEventLogs = isLegacy ? this.network.transactionManager.findEvent( - isHorizon ? 'HorizonRewardsAssigned' : 'RewardsAssigned', - this.network.contracts.RewardsManager.interface, - 'allocationID', - allocationID, - receipt, - this.logger, - ) + isHorizon ? 'HorizonRewardsAssigned' : 'RewardsAssigned', + this.network.contracts.RewardsManager.interface, + 'allocationID', + allocationID, + receipt, + this.logger, + ) : this.network.transactionManager.findEvent( - 'IndexingRewardsCollected', - this.network.contracts.SubgraphService.interface, - 'allocationId', - allocationID, - receipt, - this.logger, - ) + 'IndexingRewardsCollected', + this.network.contracts.SubgraphService.interface, + 'allocationId', + allocationID, + receipt, + this.logger, + ) const rewardsAssigned = rewardsEventLogs ? isLegacy @@ -999,10 +1015,11 @@ export class AllocationManager { }) if (params.isLegacy) { - const tx = await this.network.contracts.HorizonStaking.closeAllocation.populateTransaction( - params.allocationID, - params.poi.poi, - ) + const tx = + await this.network.contracts.HorizonStaking.closeAllocation.populateTransaction( + params.allocationID, + params.poi.poi, + ) return { protocolNetwork: params.protocolNetwork, actionID: params.actionID, @@ -1037,10 +1054,11 @@ export class AllocationManager { [params.indexer, encodeStopServiceData(params.allocationID)], ) - const tx = await this.network.contracts.SubgraphService.multicall.populateTransaction([ - collectCallData, - stopServiceCallData, - ]) + const tx = + await this.network.contracts.SubgraphService.multicall.populateTransaction([ + collectCallData, + stopServiceCallData, + ]) return { protocolNetwork: params.protocolNetwork, actionID: params.actionID, @@ -1216,17 +1234,17 @@ export class AllocationManager { }) const proof = isHorizon ? await horizonAllocationIdProof( - allocationSigner, - Number(this.network.specification.networkIdentifier.split(':')[1]), - this.network.specification.indexerOptions.address, - newAllocationId, - this.network.contracts.SubgraphService.target.toString(), - ) + allocationSigner, + Number(this.network.specification.networkIdentifier.split(':')[1]), + this.network.specification.indexerOptions.address, + newAllocationId, + this.network.contracts.SubgraphService.target.toString(), + ) : await legacyAllocationIdProof( - allocationSigner, - this.network.specification.indexerOptions.address, - newAllocationId, - ) + allocationSigner, + this.network.specification.indexerOptions.address, + newAllocationId, + ) logger.debug('Successfully generated allocation ID proof', { allocationIDProof: proof, @@ -1625,9 +1643,9 @@ export class AllocationManager { action.poi === zeroHexString ? 0n : await this.network.contracts.RewardsManager.getRewards( - this.network.contracts.HorizonStaking.target, - action.allocationID, - ) + this.network.contracts.HorizonStaking.target, + action.allocationID, + ) unallocates = unallocates + allocation.allocatedTokens } @@ -1687,10 +1705,10 @@ export class AllocationManager { throw indexerError( IndexerErrorCode.IE013, `Unfeasible action batch: Approved action batch GRT balance is ` + - `${formatGRT(batchDelta)} for horizon actions and ` + - `${formatGRT(batchDeltaLegacy)} for legacy actions ` + - `but available horizon stake equals ${formatGRT(indexerFreeStake.horizon)} ` + - `and legacy stake equals ${formatGRT(indexerFreeStake.legacy)}.`, + `${formatGRT(batchDelta)} for horizon actions and ` + + `${formatGRT(batchDeltaLegacy)} for legacy actions ` + + `but available horizon stake equals ${formatGRT(indexerFreeStake.horizon)} ` + + `and legacy stake equals ${formatGRT(indexerFreeStake.legacy)}.`, ) } } diff --git a/packages/indexer-common/src/indexer-management/types.ts b/packages/indexer-common/src/indexer-management/types.ts index 677c7ec81..05a9d3f6e 100644 --- a/packages/indexer-common/src/indexer-management/types.ts +++ b/packages/indexer-common/src/indexer-management/types.ts @@ -57,7 +57,7 @@ export interface ActionExecutionResult { export interface ExecuteTransactionResult { actionID: number success: boolean - result: ActionFailure | TransactionReceipt | "paused" | "unauthorized" + result: ActionFailure | TransactionReceipt | 'paused' | 'unauthorized' } export interface ActionFailure { diff --git a/packages/indexer-common/src/network.ts b/packages/indexer-common/src/network.ts index d8a94e97e..84689e513 100644 --- a/packages/indexer-common/src/network.ts +++ b/packages/indexer-common/src/network.ts @@ -316,7 +316,6 @@ export class Network { Tap Subgraph: ${!!tapSubgraph}.`) } - // -------------------------------------------------------------------------------- // * Graph Tally Collector // -------------------------------------------------------------------------------- diff --git a/packages/indexer-common/src/query-fees/models.ts b/packages/indexer-common/src/query-fees/models.ts index 5de5a0d4f..78570cb95 100644 --- a/packages/indexer-common/src/query-fees/models.ts +++ b/packages/indexer-common/src/query-fees/models.ts @@ -17,7 +17,8 @@ export interface ScalarTapReceiptsAttributes { } export class ScalarTapReceipts extends Model - implements ScalarTapReceiptsAttributes { + implements ScalarTapReceiptsAttributes +{ public id!: number public allocation_id!: Address public signer_address!: Address @@ -47,7 +48,8 @@ export interface TapHorizonReceiptsAttributes { } export class TapHorizonReceipts extends Model - implements TapHorizonReceiptsAttributes { + implements TapHorizonReceiptsAttributes +{ public id!: number public signer_address!: Address @@ -66,7 +68,8 @@ export class TapHorizonReceipts export class ScalarTapReceiptsInvalid extends Model - implements ScalarTapReceiptsAttributes { + implements ScalarTapReceiptsAttributes +{ public id!: number public allocation_id!: Address public signer_address!: Address @@ -82,7 +85,8 @@ export class ScalarTapReceiptsInvalid export class TapHorizonReceiptsInvalid extends Model - implements TapHorizonReceiptsAttributes { + implements TapHorizonReceiptsAttributes +{ public id!: number public signer_address!: Address @@ -111,7 +115,8 @@ export interface AllocationReceiptAttributes { export class AllocationReceipt extends Model - implements AllocationReceiptAttributes { + implements AllocationReceiptAttributes +{ public id!: string public allocation!: Address public fees!: string @@ -165,7 +170,7 @@ export interface ReceiptAggregateVoucherV2Attributes { timestampNs: bigint valueAggregate: bigint metadata: string - + last: boolean final: boolean redeemedAt: Date | null @@ -179,7 +184,6 @@ export interface FailedReceiptAggregateVoucherAttributes { reason: string } - export interface FailedReceiptAggregateVoucherV2Attributes { collectionId: string payer: string @@ -192,7 +196,8 @@ export interface FailedReceiptAggregateVoucherV2Attributes { export class ReceiptAggregateVoucher extends Model - implements ReceiptAggregateVoucherAttributes { + implements ReceiptAggregateVoucherAttributes +{ public allocationId!: Address public senderAddress!: Address public signature!: Uint8Array @@ -231,7 +236,8 @@ export type SignedRAVv2 = { export class ReceiptAggregateVoucherV2 extends Model - implements ReceiptAggregateVoucherV2Attributes { + implements ReceiptAggregateVoucherV2Attributes +{ public signature!: Uint8Array public collectionId!: string public payer!: Address @@ -274,7 +280,8 @@ export type SignedRAV = TAPVerifier.SignedRAVStruct export class FailedReceiptAggregateVoucher extends Model - implements FailedReceiptAggregateVoucherAttributes { + implements FailedReceiptAggregateVoucherAttributes +{ public allocationId!: Address public senderAddress!: Address public expectedRav!: JSON @@ -282,10 +289,10 @@ export class FailedReceiptAggregateVoucher public reason!: string } - export class FailedReceiptAggregateVoucherV2 extends Model - implements FailedReceiptAggregateVoucherV2Attributes { + implements FailedReceiptAggregateVoucherV2Attributes +{ public collectionId!: string public payer!: Address public dataService!: Address @@ -305,7 +312,8 @@ export interface TransferReceiptAttributes { export class TransferReceipt extends Model - implements TransferReceiptAttributes { + implements TransferReceiptAttributes +{ public id!: number public signer!: Address public fees!: string @@ -370,7 +378,8 @@ export interface AllocationSummaryAttributes { export class AllocationSummary extends Model - implements AllocationSummaryAttributes { + implements AllocationSummaryAttributes +{ public allocation!: Address public closedAt!: Date public createdTransfers!: number @@ -832,7 +841,6 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { }, ) - FailedReceiptAggregateVoucherV2.init( { collectionId: { @@ -1044,7 +1052,6 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { }, ) - TapHorizonReceiptsInvalid.init( { id: { From 5e601965dd817f1198ddb8f1757f9b999ba5d448 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Sat, 26 Jul 2025 00:13:20 +0000 Subject: [PATCH 2/7] fix: add @graphprotocol/horizon and @graphprotocol/subgraph-service as explicit dependencies These packages contain address book files required by @graphprotocol/toolshed but were not declared as dependencies. Adding them explicitly resolves the 'Address book path not found' errors in tests. Both packages added at version 0.4.1 (latest). --- packages/indexer-common/package.json | 2 ++ yarn.lock | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/indexer-common/package.json b/packages/indexer-common/package.json index 866db698b..5b50ff9e5 100644 --- a/packages/indexer-common/package.json +++ b/packages/indexer-common/package.json @@ -24,6 +24,8 @@ "dependencies": { "@pinax/graph-networks-registry": "0.6.7", "@graphprotocol/common-ts": "3.0.1", + "@graphprotocol/horizon": "0.4.1", + "@graphprotocol/subgraph-service": "0.4.1", "@graphprotocol/toolshed": "0.6.5", "@semiotic-labs/tap-contracts-bindings": "2.0.0", "@thi.ng/heaps": "1.2.38", diff --git a/yarn.lock b/yarn.lock index 439fb1dab..d5d4f7c57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -568,6 +568,11 @@ prom-client "14.2.0" sequelize "6.33.0" +"@graphprotocol/horizon@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@graphprotocol/horizon/-/horizon-0.4.1.tgz#b29f0944eeb9d4b50fc586e533904b9e51f18ade" + integrity sha512-YC/HVmWuCkfembwVQbPEY9NEDNsPsuHxnBMCThB1nAyai9I0Ad/nKnHZ+9baFmq5Sg1YwXxDG7Nc4SQJlgZD2g== + "@graphprotocol/interfaces@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@graphprotocol/interfaces/-/interfaces-0.2.5.tgz#b628b198f4f2c6118d6acbf29f70c2de3ba61211" @@ -583,6 +588,11 @@ split2 "^3.1.1" through2 "^3.0.1" +"@graphprotocol/subgraph-service@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@graphprotocol/subgraph-service/-/subgraph-service-0.4.1.tgz#31a76c10031aa118960a1fa0d7ba955cac123f32" + integrity sha512-vJA9lzlNQr4IRDPAbUiXM1ybizB4GzI0B1bndvB2X00ecTOWQQCiDl0W/qlApQ86+YXMjU2g90Db23ldqVOvyQ== + "@graphprotocol/toolshed@0.6.5": version "0.6.5" resolved "https://registry.yarnpkg.com/@graphprotocol/toolshed/-/toolshed-0.6.5.tgz#42fd39d1a5b9a41002c81feaecfa574f9e2dbe46" From 4de1d73b77e9f5799ccb6017383d1610b7b617df Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Sat, 26 Jul 2025 01:55:48 +0000 Subject: [PATCH 3/7] fix: update jest config to handle ESM modules in strip-ansi The newer version of strip-ansi (v7) uses ES modules which causes Jest to fail. Updated transformIgnorePatterns to transform strip-ansi and ansi-regex modules. --- packages/indexer-cli/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/indexer-cli/jest.config.js b/packages/indexer-cli/jest.config.js index d20b4b76f..55ebab263 100644 --- a/packages/indexer-cli/jest.config.js +++ b/packages/indexer-cli/jest.config.js @@ -13,7 +13,7 @@ module.exports = { // The glob patterns Jest uses to detect test files testMatch: ['**/?(*.)+(spec|test).ts?(x)'], testPathIgnorePatterns: ['/node_modules/', '/dist/', '.yalc'], - transformIgnorePatterns: ['!node_modules/'], + transformIgnorePatterns: ['node_modules/(?!(strip-ansi|ansi-regex)/)'], testTimeout: 60000, globals: { __DATABASE__: { From 62931d4e8d8b75371bacc4f78fdb3ad2fb02037f Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 28 Jul 2025 14:01:33 +0000 Subject: [PATCH 4/7] fix: remove transformIgnorePatterns in indexer-cli tests --- packages/indexer-cli/jest.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/indexer-cli/jest.config.js b/packages/indexer-cli/jest.config.js index 55ebab263..0eeafcaef 100644 --- a/packages/indexer-cli/jest.config.js +++ b/packages/indexer-cli/jest.config.js @@ -13,7 +13,6 @@ module.exports = { // The glob patterns Jest uses to detect test files testMatch: ['**/?(*.)+(spec|test).ts?(x)'], testPathIgnorePatterns: ['/node_modules/', '/dist/', '.yalc'], - transformIgnorePatterns: ['node_modules/(?!(strip-ansi|ansi-regex)/)'], testTimeout: 60000, globals: { __DATABASE__: { From fd23391ec25f8642ea839c31ad3080c6795f99b2 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 28 Jul 2025 14:11:09 +0000 Subject: [PATCH 5/7] fix: replace strip-ansi in tests with a regex --- packages/indexer-cli/src/__tests__/util.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/indexer-cli/src/__tests__/util.ts b/packages/indexer-cli/src/__tests__/util.ts index bf8f1b9f3..8839edc05 100644 --- a/packages/indexer-cli/src/__tests__/util.ts +++ b/packages/indexer-cli/src/__tests__/util.ts @@ -5,7 +5,6 @@ import { Socket } from 'net' import { URL } from 'url' import path from 'path' import { Sequelize } from 'sequelize' -import stripAnsi from 'strip-ansi' import { ActionStatus, ActionType, @@ -56,6 +55,22 @@ let metrics: Metrics const yamlObj = loadTestYamlConfig() const testNetworkSpecification = specification.NetworkSpecification.parse(yamlObj) +// Replace strip-ansi with a simple function using the same regex pattern +// Based on ansi-regex v6.1.0 pattern +function stripAnsi(str: string): string { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string, got ${typeof str}`) + } + + // Regex pattern from ansi-regex + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))' + ].join('|') + + return str.replace(new RegExp(pattern, 'g'), '') +} + export const setupMultiNetworks = async () => { return await setup(true) } From 15871a916d2e1c355b21fd30e5f86b8e58ed3b2d Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 28 Jul 2025 14:36:43 +0000 Subject: [PATCH 6/7] fix: error msg --- .../references/indexer-rules-invalid-set-arg.stdout | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rules-invalid-set-arg.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rules-invalid-set-arg.stdout index 332674874..50ffde39a 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rules-invalid-set-arg.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rules-invalid-set-arg.stdout @@ -1,4 +1 @@ -Error: Indexing rule attribute 'allocationAmoewt' not supported, did you mean? -- allocationAmount -- allocationLifetime -- maxAllocationPercentage +Error: allocationAmoewt From c8a7d8699cfa4574121fc7451a762a00148e4b27 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 28 Jul 2025 15:49:24 +0000 Subject: [PATCH 7/7] fix: update indexer-cli test references for new action columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated test reference files to include the three new columns added in the horizon branch: - publicPOI and poiBlockNumber (added in commit 325d6fa2) - isLegacy (added in commit d2bfd5ba) This fixes the failing actions tests in CI. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../references/indexer-actions-get-first.stdout | 10 +++++----- .../references/indexer-actions-get.stdout | 14 +++++++------- packages/indexer-cli/src/__tests__/util.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout b/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout index 8a9613df1..dd32934db 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout @@ -1,5 +1,5 @@ -┌────┬──────────────────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────┬──────────┬────────┬────────┬───────────────┬─────────────┬────────┐ -│ id │ protocolNetwork │ type │ deploymentID │ allocationID │ amount │ poi │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ -├────┼──────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼────────┼────────┼───────────────┼─────────────┼────────┤ -│ 2 │ arbitrum-sepolia │ unallocate │ QmSrf6VVPyg9NGdS1xhLmoosk3qZQaWhfoSTHE2H7sht6Q │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ -└────┴──────────────────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────┴──────────┴────────┴────────┴───────────────┴─────────────┴────────┘ +┌────┬──────────────────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────────┬────────────────┬───────┬──────────┬────────┬────────┬───────────────┬─────────────┬────────┬──────────┐ +│ id │ protocolNetwork │ type │ deploymentID │ allocationID │ amount │ poi │ publicPOI │ poiBlockNumber │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ isLegacy │ +├────┼──────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────────┼────────────────┼───────┼──────────┼────────┼────────┼───────────────┼─────────────┼────────┼──────────┤ +│ 2 │ arbitrum-sepolia │ unallocate │ QmSrf6VVPyg9NGdS1xhLmoosk3qZQaWhfoSTHE2H7sht6Q │ null │ null │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ false │ +└────┴──────────────────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────────┴────────────────┴───────┴──────────┴────────┴────────┴───────────────┴─────────────┴────────┴──────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout b/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout index 1d0ac63da..1055b1e43 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout @@ -1,7 +1,7 @@ -┌────┬──────────────────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────┬──────────┬─────────┬────────┬───────────────┬─────────────┬────────┐ -│ id │ protocolNetwork │ type │ deploymentID │ allocationID │ amount │ poi │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ -├────┼──────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┤ -│ 2 │ arbitrum-sepolia │ unallocate │ QmSrf6VVPyg9NGdS1xhLmoosk3qZQaWhfoSTHE2H7sht6Q │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ -├────┼──────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┤ -│ 1 │ arbitrum-sepolia │ allocate │ QmSrf6VVPyg9NGdS1xhLmoosk3qZQaWhfoSTHE2H7sht6Q │ null │ null │ null │ null │ 0 │ success │ test │ null │ null │ test │ -└────┴──────────────────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────┴──────────┴─────────┴────────┴───────────────┴─────────────┴────────┘ +┌────┬──────────────────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────────┬────────────────┬───────┬──────────┬─────────┬────────┬───────────────┬─────────────┬────────┬──────────┐ +│ id │ protocolNetwork │ type │ deploymentID │ allocationID │ amount │ poi │ publicPOI │ poiBlockNumber │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ isLegacy │ +├────┼──────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────────┼────────────────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┼──────────┤ +│ 2 │ arbitrum-sepolia │ unallocate │ QmSrf6VVPyg9NGdS1xhLmoosk3qZQaWhfoSTHE2H7sht6Q │ null │ null │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ false │ +├────┼──────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────────┼────────────────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┼──────────┤ +│ 1 │ arbitrum-sepolia │ allocate │ QmSrf6VVPyg9NGdS1xhLmoosk3qZQaWhfoSTHE2H7sht6Q │ null │ null │ null │ null │ null │ null │ 0 │ success │ test │ null │ null │ test │ false │ +└────┴──────────────────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────────┴────────────────┴───────┴──────────┴─────────┴────────┴───────────────┴─────────────┴────────┴──────────┘ diff --git a/packages/indexer-cli/src/__tests__/util.ts b/packages/indexer-cli/src/__tests__/util.ts index 8839edc05..41df97e13 100644 --- a/packages/indexer-cli/src/__tests__/util.ts +++ b/packages/indexer-cli/src/__tests__/util.ts @@ -65,7 +65,7 @@ function stripAnsi(str: string): string { // Regex pattern from ansi-regex const pattern = [ '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))' + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))', ].join('|') return str.replace(new RegExp(pattern, 'g'), '')