diff --git a/indexer/packages/postgres/src/types/perpetual-market-types.ts b/indexer/packages/postgres/src/types/perpetual-market-types.ts index 9f75a625d09..4a1c5215f23 100644 --- a/indexer/packages/postgres/src/types/perpetual-market-types.ts +++ b/indexer/packages/postgres/src/types/perpetual-market-types.ts @@ -37,6 +37,7 @@ export interface PerpetualMarketUpdateObject { subticksPerTick?: number, stepBaseQuantums?: number, liquidityTierId?: number, + defaultFundingRate1H?: string, } export enum PerpetualMarketColumns { diff --git a/indexer/packages/redis/__tests__/caches/next-funding-cache.test.ts b/indexer/packages/redis/__tests__/caches/next-funding-cache.test.ts index 24d6e95ff48..886914d71ce 100644 --- a/indexer/packages/redis/__tests__/caches/next-funding-cache.test.ts +++ b/indexer/packages/redis/__tests__/caches/next-funding-cache.test.ts @@ -17,8 +17,14 @@ describe('nextFundingCache', () => { await addFundingSample('BTC', new Big('0.0001'), client); await addFundingSample('BTC', new Big('0.0002'), client); // avg = 0.00015 await addFundingSample('ETH', new Big('0.0005'), client); // avg = 0.0005 - expect(await getNextFunding(client, ['BTC', 'ETH'])).toEqual( - { BTC: new Big('0.00015'), ETH: new Big('0.0005') }, + expect(await getNextFunding(client, [ + ['BTC', '0.0001'], + ['ETH', '0'], + ])).toEqual( + { + BTC: new Big('0.00025'), // 0.00015 + 0.0001 + ETH: new Big('0.0005'), // 0.0005 + 0 + }, ); }); @@ -27,14 +33,22 @@ describe('nextFundingCache', () => { await addFundingSample('BTC', new Big('0.0002'), client); // avg = 0.00015 await clearFundingSamples('BTC', client); await addFundingSample('ETH', new Big('0.0005'), client); // avg = 0.0005 - expect(await getNextFunding(client, ['BTC', 'ETH'])).toEqual( - { BTC: undefined, ETH: new Big('0.0005') }, + expect(await getNextFunding(client, [ + ['BTC', '0.0001'], + ['ETH', '0.00015'], + ])).toEqual( + { + BTC: new Big('0.0001'), // no samples, default to 0.0001 + ETH: new Big('0.00065'), // 0.0005 + 0.00015 + }, ); }); it('get next funding with no values', async () => { - expect(await getNextFunding(client, ['BTC'])).toEqual( - { BTC: undefined }, + expect(await getNextFunding(client, [ + ['BTC', '0'], + ])).toEqual( + { BTC: new Big('0') }, ); }); }); diff --git a/indexer/packages/redis/src/caches/next-funding-cache.ts b/indexer/packages/redis/src/caches/next-funding-cache.ts index cff104ae53f..fcdf81419e5 100644 --- a/indexer/packages/redis/src/caches/next-funding-cache.ts +++ b/indexer/packages/redis/src/caches/next-funding-cache.ts @@ -20,11 +20,11 @@ function getKey(ticker: string): string { */ export async function getNextFunding( client: RedisClient, - tickers: string[], -): Promise<{ [ticker: string]: Big | undefined }> { - const fundingRates: { [ticker: string]: Big | undefined } = {}; + tickerDefaultFundingRate1HPairs: [string, string][], +): Promise<{ [ticker: string]: Big }> { + const fundingRates: { [ticker: string]: Big } = {}; await Promise.all( - tickers.map(async (ticker: string) => { + tickerDefaultFundingRate1HPairs.map(async ([ticker, defaultFundingRate1H]) => { const rates: string[] = await lRangeAsync( getKey(ticker), client, @@ -36,9 +36,9 @@ export async function getNextFunding( new Big(0), ); const avg: Big = sum.div(rates.length); - fundingRates[ticker] = avg; + fundingRates[ticker] = avg.plus(new Big(defaultFundingRate1H)); } else { - fundingRates[ticker] = undefined; + fundingRates[ticker] = new Big(defaultFundingRate1H); } }), ); diff --git a/indexer/services/ender/__tests__/handlers/funding-handler.test.ts b/indexer/services/ender/__tests__/handlers/funding-handler.test.ts index afe0df78fa2..6cb3ea035f4 100644 --- a/indexer/services/ender/__tests__/handlers/funding-handler.test.ts +++ b/indexer/services/ender/__tests__/handlers/funding-handler.test.ts @@ -12,6 +12,7 @@ import { FundingIndexUpdatesColumns, FundingIndexUpdatesFromDatabase, FundingIndexUpdatesTable, + PerpetualMarketTable, OraclePriceTable, Ordering, perpetualMarketRefresher, @@ -42,6 +43,7 @@ import Big from 'big.js'; import { redisClient } from '../../src/helpers/redis/redis-controller'; import { bigIntToBytes } from '@dydxprotocol-indexer/v4-proto-parser'; import { createPostgresFunctions } from '../../src/helpers/postgres/postgres-functions'; +import { defaultPerpetualMarket } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; describe('fundingHandler', () => { beforeAll(async () => { @@ -127,10 +129,54 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage); await expectNextFundingRate( - 'BTC-USD', new Big(protocolTranslations.funding8HourValuePpmTo1HourRate( defaultFundingUpdateSampleEvent.updates[0].fundingValuePpm, )), + 'BTC-USD', + ); + }); + + it.each([ + [ + 'Non-zero Default funding', + 'TEST-USD', + '0.0001', + ], + ])('(%s) non-zero default funding rate', async ( + _name: string, + ticker: string, + defaultFundingRate1H: string, + ) => { + const testPerpetualMarket = await PerpetualMarketTable.create({ + ...defaultPerpetualMarket, + id: '1000', // Different id than `defaultPerpeptualMarket` to avoid conflict + ticker, + defaultFundingRate1H, + }); + + const fundingUpdateSampleEvent: FundingEventV1 = { + type: FundingEventV1_Type.TYPE_PREMIUM_SAMPLE, + updates: [ + { + perpetualId: parseInt(testPerpetualMarket.id, 10), + fundingValuePpm: 120, + fundingIndex: bigIntToBytes(BigInt(0)), + }, + ], + }; + + const kafkaMessage: KafkaMessage = createKafkaMessageFromFundingEvents({ + fundingEvents: [fundingUpdateSampleEvent], + height: defaultHeight, + time: defaultTime, + }); + + await onMessage(kafkaMessage); + + await expectNextFundingRate( + new Big('0.000115'), // 0.000120 / 8 + default 0.0001 + ticker, + defaultFundingRate1H, ); }); @@ -160,14 +206,14 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage); await expectNextFundingRate( - 'BTC-USD', new Big('0.000006875'), + 'BTC-USD', ); await expectNextFundingRate( - 'ETH-USD', new Big(protocolTranslations.funding8HourValuePpmTo1HourRate( fundingUpdateSampleEvent2.updates[1].fundingValuePpm, )), + 'ETH-USD', ); }); @@ -204,10 +250,10 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage); await expectNextFundingRate( - 'BTC-USD', new Big(protocolTranslations.funding8HourValuePpmTo1HourRate( defaultFundingUpdateSampleEvent.updates[0].fundingValuePpm, )), + 'BTC-USD', ); const kafkaMessage2: KafkaMessage = createKafkaMessageFromFundingEvents({ @@ -218,8 +264,8 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage2); await expectNextFundingRate( + new Big('0'), 'BTC-USD', - undefined, ); const fundingIndices: FundingIndexUpdatesFromDatabase[] = await FundingIndexUpdatesTable.findAll({}, [], {}); @@ -253,10 +299,10 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage); await expectNextFundingRate( - 'BTC-USD', new Big(protocolTranslations.funding8HourValuePpmTo1HourRate( defaultFundingUpdateSampleEvent.updates[0].fundingValuePpm, )), + 'BTC-USD', ); const kafkaMessage2: KafkaMessage = createKafkaMessageFromFundingEvents({ @@ -267,8 +313,8 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage2); await expectNextFundingRate( + new Big(0), 'BTC-USD', - undefined, ); const fundingIndices: FundingIndexUpdatesFromDatabase[] = await FundingIndexUpdatesTable.findAll({}, [], {}); @@ -310,16 +356,16 @@ describe('fundingHandler', () => { await Promise.all([ expectNextFundingRate( - 'BTC-USD', new Big(protocolTranslations.funding8HourValuePpmTo1HourRate( fundingSampleEvent.updates[0].fundingValuePpm, )), + 'BTC-USD', ), expectNextFundingRate( - 'ETH-USD', new Big(protocolTranslations.funding8HourValuePpmTo1HourRate( fundingSampleEvent.updates[1].fundingValuePpm, )), + 'ETH-USD', ), ]); @@ -347,12 +393,12 @@ describe('fundingHandler', () => { await onMessage(kafkaMessage2); await Promise.all([ expectNextFundingRate( + new Big('0'), 'BTC-USD', - undefined, ), expectNextFundingRate( + new Big('0'), 'ETH-USD', - undefined, ), ]); const fundingIndices: FundingIndexUpdatesFromDatabase[] = await diff --git a/indexer/services/ender/__tests__/helpers/redis-helpers.ts b/indexer/services/ender/__tests__/helpers/redis-helpers.ts index 7f2ec30800a..05e240fa37b 100644 --- a/indexer/services/ender/__tests__/helpers/redis-helpers.ts +++ b/indexer/services/ender/__tests__/helpers/redis-helpers.ts @@ -9,14 +9,15 @@ import Big from 'big.js'; import { redisClient } from '../../src/helpers/redis/redis-controller'; export async function expectNextFundingRate( + expectedRate: Big, ticker: string, - rate: Big | undefined, + defaultFundingRate1H: string = '0', ): Promise { - const rates: { [ticker: string]: Big | undefined } = await NextFundingCache.getNextFunding( + const rates: { [ticker: string]: Big } = await NextFundingCache.getNextFunding( redisClient, - [ticker], + [[ticker, defaultFundingRate1H]], ); - expect(rates[ticker]).toEqual(rate); + expect(rates[ticker]).toEqual(expectedRate); } export async function expectStateFilledQuantums( diff --git a/indexer/services/roundtable/src/tasks/market-updater.ts b/indexer/services/roundtable/src/tasks/market-updater.ts index ab6d2ea30ec..4047b2a283c 100644 --- a/indexer/services/roundtable/src/tasks/market-updater.ts +++ b/indexer/services/roundtable/src/tasks/market-updater.ts @@ -52,7 +52,14 @@ export default async function runTask(): Promise { PerpetualMarketFromDatabase[] = await PerpetualMarketTable.findAll({}, []); const perpetualMarketIds: string[] = _.map(perpetualMarkets, PerpetualMarketColumns.id); const clobPairIds: string[] = _.map(perpetualMarkets, PerpetualMarketColumns.clobPairId); - const tickers: string[] = _.map(perpetualMarkets, PerpetualMarketColumns.ticker); + const tickerDefaultFundingRate1HPairs: [string, string][] = _.map( + perpetualMarkets, + (market) => [ + market[PerpetualMarketColumns.ticker], + // Use 0 as default for null default funding rate + market[PerpetualMarketColumns.defaultFundingRate1H] ?? '0', + ], + ); const latestPrices: PriceMap = await OraclePriceTable.getLatestPrices(); const prices24hAgo: PriceMap = await OraclePriceTable.getPricesFrom24hAgo(); @@ -65,13 +72,12 @@ export default async function runTask(): Promise { ]: [ _.Dictionary, _.Dictionary, - _.Dictionary, + _.Dictionary, ] = await Promise.all([ // TODO(DEC-1149 Add support for pulling information from candles FillTable.get24HourInformation(clobPairIds), PerpetualPositionTable.getOpenInterestLong(perpetualMarketIds), - // TODO(CT-1340): Need to add default funding rate to this value. - NextFundingCache.getNextFunding(redisClient, tickers), + NextFundingCache.getNextFunding(redisClient, tickerDefaultFundingRate1HPairs), ]); stats.timing(