diff --git a/packages/indexer-common/src/allocations/__tests__/tap.test.ts b/packages/indexer-common/src/allocations/__tests__/tap.test.ts index f0697d4d3..cf61f7a81 100644 --- a/packages/indexer-common/src/allocations/__tests__/tap.test.ts +++ b/packages/indexer-common/src/allocations/__tests__/tap.test.ts @@ -2,9 +2,11 @@ import { defineQueryFeeModels, GraphNode, Network, + EscrowAccounts, QueryFeeModels, TapSubgraphResponse, TapCollector, + Allocation, } from '@graphprotocol/indexer-common' import { Address, @@ -499,6 +501,36 @@ describe('TAP', () => { }, timeout, ) + + test('test `submitRAVs` with escrow account lower on balance', async () => { + // mock redeemRav to not call the blockchain + const redeemRavFunc = jest + .spyOn(tapCollector, 'redeemRav') + .mockImplementation(jest.fn()) + + // mock fromResponse to return the correct escrow account + // eslint-disable-next-line @typescript-eslint/no-unused-vars + jest.spyOn(EscrowAccounts, 'fromResponse').mockImplementation((_) => { + const balances = new Map
() + balances.set(SENDER_ADDRESS_1, 40000000000000n) + return new EscrowAccounts(balances) + }) + + const [first] = await queryFeeModels.receiptAggregateVouchers.findAll() + const rav = first.getSignedRAV() + + const ravWithAllocation = { + rav, + allocation: {} as Allocation, + sender: first.senderAddress, + } + const ravs = [ravWithAllocation, ravWithAllocation, ravWithAllocation] + // submit 3 ravs + await tapCollector['submitRAVs'](ravs) + // expect to be able to redeem only 2 of them + // because of the balance + expect(redeemRavFunc).toBeCalledTimes(2) + }) }) function createLastNonFinalRav( diff --git a/packages/indexer-common/src/allocations/index.ts b/packages/indexer-common/src/allocations/index.ts index a52b572fb..79e60e617 100644 --- a/packages/indexer-common/src/allocations/index.ts +++ b/packages/indexer-common/src/allocations/index.ts @@ -1,3 +1,4 @@ +export * from './escrow-accounts' export * from './keys' export * from './query-fees' export * from './tap-collector' diff --git a/packages/indexer-common/src/allocations/tap-collector.ts b/packages/indexer-common/src/allocations/tap-collector.ts index 67b683ad5..27a6cc029 100644 --- a/packages/indexer-common/src/allocations/tap-collector.ts +++ b/packages/indexer-common/src/allocations/tap-collector.ts @@ -58,7 +58,7 @@ interface ValidRavs { eligible: RavWithAllocation[] } -interface RavWithAllocation { +export interface RavWithAllocation { rav: SignedRAV allocation: Allocation sender: Address @@ -450,7 +450,6 @@ export class TapCollector { ) return } - const escrow = this.tapContracts logger.info(`Redeem last RAVs on chain individually`, { signedRavs, @@ -481,54 +480,10 @@ export class TapCollector { allocation: rav.allocationId, }) try { - const proof = await tapAllocationIdProof( - allocationSigner(this.transactionManager.wallet, allocation), - parseInt(this.protocolNetwork.split(':')[1]), - sender, - toAddress(rav.allocationId), - toAddress(escrow.escrow.address), - ) - this.logger.debug(`Computed allocationIdProof`, { - allocationId: rav.allocationId, - proof, - }) - // Submit the signed RAV on chain - const txReceipt = await this.transactionManager.executeTransaction( - () => escrow.escrow.estimateGas.redeem(signedRav, proof), - (gasLimit) => - escrow.escrow.redeem(signedRav, proof, { - gasLimit, - }), - logger.child({ function: 'redeem' }), - ) - - // get tx receipt and post process - if (txReceipt === 'paused' || txReceipt === 'unauthorized') { - this.metrics.ravRedeemsInvalid.inc({ allocation: rav.allocationId }) - return - } + await this.redeemRav(logger, allocation, sender, signedRav) // subtract from the escrow account // THIS IS A MUT OPERATION escrowAccounts.subtractSenderBalance(sender, ravValue) - - this.metrics.ravCollectedFees.set( - { allocation: rav.allocationId }, - parseFloat(rav.valueAggregate.toString()), - ) - - try { - await this.markRavAsRedeemed(toAddress(rav.allocationId), sender) - logger.info( - `Updated receipt aggregate vouchers table with redeemed_at for allocation ${rav.allocationId} and sender ${sender}`, - ) - } catch (err) { - logger.warn( - `Failed to update receipt aggregate voucher table with redeemed_at for allocation ${rav.allocationId}`, - { - err, - }, - ) - } } catch (err) { this.metrics.ravRedeemsFailed.inc({ allocation: rav.allocationId }) logger.error(`Failed to redeem RAV`, { @@ -571,6 +526,63 @@ export class TapCollector { ) } + public async redeemRav( + logger: Logger, + allocation: Allocation, + sender: Address, + signedRav: SignedRAV, + ) { + const { rav } = signedRav + + const escrow = this.tapContracts + + const proof = await tapAllocationIdProof( + allocationSigner(this.transactionManager.wallet, allocation), + parseInt(this.protocolNetwork.split(':')[1]), + sender, + toAddress(rav.allocationId), + toAddress(escrow.escrow.address), + ) + this.logger.debug(`Computed allocationIdProof`, { + allocationId: rav.allocationId, + proof, + }) + // Submit the signed RAV on chain + const txReceipt = await this.transactionManager.executeTransaction( + () => escrow.escrow.estimateGas.redeem(signedRav, proof), + (gasLimit) => + escrow.escrow.redeem(signedRav, proof, { + gasLimit, + }), + logger.child({ function: 'redeem' }), + ) + + // get tx receipt and post process + if (txReceipt === 'paused' || txReceipt === 'unauthorized') { + this.metrics.ravRedeemsInvalid.inc({ allocation: rav.allocationId }) + return + } + + this.metrics.ravCollectedFees.set( + { allocation: rav.allocationId }, + parseFloat(rav.valueAggregate.toString()), + ) + + try { + await this.markRavAsRedeemed(toAddress(rav.allocationId), sender) + logger.info( + `Updated receipt aggregate vouchers table with redeemed_at for allocation ${rav.allocationId} and sender ${sender}`, + ) + } catch (err) { + logger.warn( + `Failed to update receipt aggregate voucher table with redeemed_at for allocation ${rav.allocationId}`, + { + err, + }, + ) + } + } + private async markRavAsRedeemed( allocationId: Address, senderAddress: Address,