diff --git a/observer/bin/dry-run.js b/observer/bin/dry-run.js index a1a15fc..fa862be 100644 --- a/observer/bin/dry-run.js +++ b/observer/bin/dry-run.js @@ -20,4 +20,4 @@ await Promise.all([ observeScheduledRewards(pgPools, ieContract) ]) -await pgPools.end() +await pgPools.stats.end() diff --git a/observer/lib/observer.js b/observer/lib/observer.js index 26bbf69..b654cfb 100644 --- a/observer/lib/observer.js +++ b/observer/lib/observer.js @@ -9,9 +9,9 @@ import * as Sentry from '@sentry/node' */ export const observeTransferEvents = async (pgPoolStats, ieContract, provider) => { const { rows } = await pgPoolStats.query( - 'SELECT MAX(last_checked_block) FROM daily_reward_transfers' + 'SELECT MAX(last_checked_block) AS last_checked_block FROM daily_reward_transfers' ) - let queryFromBlock = rows[0].last_checked_block + let queryFromBlock = rows[0].last_checked_block + 1 const currentBlockNumber = await provider.getBlockNumber() if (!queryFromBlock || queryFromBlock < currentBlockNumber - 1900) { @@ -31,6 +31,8 @@ export const observeTransferEvents = async (pgPoolStats, ieContract, provider) = console.log('Transfer event:', transferEvent) await updateDailyTransferStats(pgPoolStats, transferEvent, currentBlockNumber) } + + return events.length } /** diff --git a/observer/test/observer.test.js b/observer/test/observer.test.js index 80c6c7e..b43ae6b 100644 --- a/observer/test/observer.test.js +++ b/observer/test/observer.test.js @@ -1,19 +1,132 @@ import assert from 'node:assert' -import { observeScheduledRewards } from '../lib/observer.js' +import { beforeEach, describe, it } from 'mocha' import { getPgPools } from '@filecoin-station/spark-stats-db' import { givenDailyParticipants } from 'spark-evaluate/test/helpers/queries.js' -const getDayAsISOString = d => d.toISOString().split('T')[0] -const today = () => getDayAsISOString(new Date()) +import { observeTransferEvents, observeScheduledRewards } from '../lib/observer.js' describe('observer', () => { - describe('observeScheduledRewards', () => { - let pgPools + let pgPools + const getDayAsISOString = d => d.toISOString().split('T')[0] + const today = () => getDayAsISOString(new Date()) + + before(async () => { + pgPools = await getPgPools() + }) + + after(async () => { + await pgPools.end() + }) + + describe('observeTransferEvents', () => { + let ieContractMock + let providerMock - before(async () => { - pgPools = await getPgPools() + beforeEach(async () => { + await pgPools.stats.query('DELETE FROM daily_reward_transfers') + + ieContractMock = { + filters: { + Transfer: () => 'TransferEventFilter' + }, + queryFilter: async () => [] + } + providerMock = { + getBlockNumber: async () => 2000 + } }) + it('should correctly observe and update transfer events', async () => { + ieContractMock.queryFilter = async (eventName, fromBlock) => { + const events = [ + { args: { to: 'address1', amount: 100 }, blockNumber: 2000 }, + { args: { to: 'address1', amount: 200 }, blockNumber: 2000 } + ] + return events.filter((event) => event.blockNumber >= fromBlock) + } + + await observeTransferEvents(pgPools.stats, ieContractMock, providerMock) + + const { rows } = await pgPools.stats.query(` + SELECT day::TEXT, to_address, amount, last_checked_block FROM daily_reward_transfers + `) + assert.strictEqual(rows.length, 1) + assert.deepStrictEqual(rows, [{ + day: today(), to_address: 'address1', amount: '300', last_checked_block: 2000 + }]) + }) + + it('should handle multiple addresses in transfer events', async () => { + ieContractMock.queryFilter = async (eventName, fromBlock) => { + const events = [ + { args: { to: 'address1', amount: 50 }, blockNumber: 2000 }, + { args: { to: 'address2', amount: 150 }, blockNumber: 2000 } + ] + return events.filter((event) => event.blockNumber >= fromBlock) + } + + await observeTransferEvents(pgPools.stats, ieContractMock, providerMock) + + const { rows } = await pgPools.stats.query(` + SELECT day::TEXT, to_address, amount, last_checked_block FROM daily_reward_transfers + ORDER BY to_address + `) + assert.strictEqual(rows.length, 2) + assert.deepStrictEqual(rows, [ + { day: today(), to_address: 'address1', amount: '50', last_checked_block: 2000 }, + { day: today(), to_address: 'address2', amount: '150', last_checked_block: 2000 } + ]) + }) + + it('should not duplicate transfer events', async () => { + ieContractMock.queryFilter = async (eventName, fromBlock) => { + const events = [ + { args: { to: 'address1', amount: 50 }, blockNumber: 2000 }, + { args: { to: 'address1', amount: 50 }, blockNumber: 2000 } + ] + return events.filter((event) => event.blockNumber >= fromBlock) + } + + const numEvents1 = await observeTransferEvents(pgPools.stats, ieContractMock, providerMock) + assert.strictEqual(numEvents1, 2) + + const numEvents2 = await observeTransferEvents(pgPools.stats, ieContractMock, providerMock) + assert.strictEqual(numEvents2, 0) + + const { rows } = await pgPools.stats.query(` + SELECT day::TEXT, to_address, amount, last_checked_block FROM daily_reward_transfers + `) + assert.strictEqual(rows.length, 1) + assert.deepStrictEqual(rows, [{ + day: today(), to_address: 'address1', amount: '100', last_checked_block: 2000 + }]) + }) + + it('should avoid querying too old blocks', async () => { + providerMock.getBlockNumber = async () => 2500 + ieContractMock.queryFilter = async (eventName, fromBlock) => { + const events = [ + { args: { to: 'address1', amount: 50 }, blockNumber: 400 }, + { args: { to: 'address2', amount: 150 }, blockNumber: 400 }, + { args: { to: 'address1', amount: 250 }, blockNumber: 2000 } + ] + return events.filter((event) => event.blockNumber >= fromBlock) + } + + await observeTransferEvents(pgPools.stats, ieContractMock, providerMock) + + const { rows } = await pgPools.stats.query(` + SELECT day::TEXT, to_address, amount, last_checked_block FROM daily_reward_transfers + ORDER BY to_address + `) + assert.strictEqual(rows.length, 1) + assert.deepStrictEqual(rows, [ + { day: today(), to_address: 'address1', amount: '250', last_checked_block: 2500 } + ]) + }) + }) + + describe('observeScheduledRewards', () => { beforeEach(async () => { await pgPools.evaluate.query('DELETE FROM daily_participants') await pgPools.evaluate.query('DELETE FROM participants')