From 0330ae7db89b185ce548c672a226b4635d1f19d3 Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:02:36 -0400 Subject: [PATCH] [TRA-571] Add vault table and vault event version/subtype. (#2263) --- .../postgres/__tests__/db/migrations.test.ts | 2 +- .../postgres/__tests__/helpers/constants.ts | 14 +++ .../__tests__/stores/vault-table.test.ts | 81 ++++++++++++++ .../20240912180829_create_vaults_table.ts | 20 ++++ .../postgres/src/helpers/db-helpers.ts | 1 + .../postgres/src/models/vault-model.ts | 44 ++++++++ .../postgres/src/stores/vault-table.ts | 100 ++++++++++++++++++ .../postgres/src/types/db-model-types.ts | 9 ++ indexer/packages/postgres/src/types/index.ts | 1 + .../postgres/src/types/query-types.ts | 6 ++ .../postgres/src/types/vault-types.ts | 24 +++++ protocol/indexer/events/constants.go | 3 + 12 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 indexer/packages/postgres/__tests__/stores/vault-table.test.ts create mode 100644 indexer/packages/postgres/src/db/migrations/migration_files/20240912180829_create_vaults_table.ts create mode 100644 indexer/packages/postgres/src/models/vault-model.ts create mode 100644 indexer/packages/postgres/src/stores/vault-table.ts create mode 100644 indexer/packages/postgres/src/types/vault-types.ts diff --git a/indexer/packages/postgres/__tests__/db/migrations.test.ts b/indexer/packages/postgres/__tests__/db/migrations.test.ts index faa4a74c9c..e41d405416 100644 --- a/indexer/packages/postgres/__tests__/db/migrations.test.ts +++ b/indexer/packages/postgres/__tests__/db/migrations.test.ts @@ -12,7 +12,7 @@ import { seedData } from '../helpers/mock-generators'; // NOTE: If a model is modified for a migration then these // tests must be skipped until the following migration -describe.skip('Test new migration', () => { +describe('Test new migration', () => { beforeEach(async () => { await migrate(); }); diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index f306550591..fffe66d289 100644 --- a/indexer/packages/postgres/__tests__/helpers/constants.ts +++ b/indexer/packages/postgres/__tests__/helpers/constants.ts @@ -60,6 +60,8 @@ import { TransferCreateObject, WalletCreateObject, PersistentCacheCreateObject, + VaultCreateObject, + VaultStatus, } from '../../src/types'; import { denomToHumanReadableConversion } from './conversion-helpers'; @@ -991,3 +993,15 @@ export const defaultFirebaseNotificationToken = { language: 'en', updatedAt: createdDateTime.toISO(), }; + +// ============== Vaults ============= + +export const defaultVaultAddress: string = 'dydx1pzaql7h3tkt9uet8yht80me5td6gh0aprf58yk'; + +export const defaultVault: VaultCreateObject = { + address: defaultVaultAddress, + clobPairId: '0', + status: VaultStatus.QUOTING, + createdAt: createdDateTime.toISO(), + updatedAt: createdDateTime.toISO(), +}; diff --git a/indexer/packages/postgres/__tests__/stores/vault-table.test.ts b/indexer/packages/postgres/__tests__/stores/vault-table.test.ts new file mode 100644 index 0000000000..134e45f337 --- /dev/null +++ b/indexer/packages/postgres/__tests__/stores/vault-table.test.ts @@ -0,0 +1,81 @@ +import * as VaultTable from '../../src/stores/vault-table'; +import { + clearData, + migrate, + teardown, +} from '../../src/helpers/db-helpers'; +import { defaultVault, defaultAddress } from '../helpers/constants'; +import { VaultFromDatabase, VaultStatus } from '../../src/types'; + +describe('Vault store', () => { + beforeAll(async () => { + await migrate(); + }); + + afterEach(async () => { + await clearData(); + }); + + afterAll(async () => { + await teardown(); + }); + + it('Successfully creates a vault', async () => { + await VaultTable.create(defaultVault); + }); + + it('Successfully finds all vaults', async () => { + await Promise.all([ + VaultTable.create(defaultVault), + VaultTable.create({ + ...defaultVault, + address: defaultAddress, + clobPairId: '1', + }), + ]); + + const vaults: VaultFromDatabase[] = await VaultTable.findAll( + {}, + [], + { readReplica: true }, + ); + + expect(vaults.length).toEqual(2); + expect(vaults[0]).toEqual(expect.objectContaining(defaultVault)); + expect(vaults[1]).toEqual(expect.objectContaining({ + ...defaultVault, + address: defaultAddress, + clobPairId: '1', + })); + }); + + it('Succesfully upserts a vault', async () => { + await VaultTable.create(defaultVault); + + let vaults: VaultFromDatabase[] = await VaultTable.findAll( + {}, + [], + { readReplica: true }, + ); + + expect(vaults.length).toEqual(1); + expect(vaults[0]).toEqual(expect.objectContaining(defaultVault)); + + await VaultTable.upsert({ + ...defaultVault, + status: VaultStatus.CLOSE_ONLY, + }); + + vaults = await VaultTable.findAll( + {}, + [], + { readReplica: true }, + ); + + expect(vaults.length).toEqual(1); + expect(vaults[0]).toEqual(expect.objectContaining({ + ...defaultVault, + status: VaultStatus.CLOSE_ONLY, + })); + }); +}); diff --git a/indexer/packages/postgres/src/db/migrations/migration_files/20240912180829_create_vaults_table.ts b/indexer/packages/postgres/src/db/migrations/migration_files/20240912180829_create_vaults_table.ts new file mode 100644 index 0000000000..7c869cecd9 --- /dev/null +++ b/indexer/packages/postgres/src/db/migrations/migration_files/20240912180829_create_vaults_table.ts @@ -0,0 +1,20 @@ +import * as Knex from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema.createTable('vaults', (table) => { + table.string('address').primary().notNullable(); // address of vault + table.bigInteger('clobPairId').notNullable(); // clob pair id for vault + table.enum('status', [ + 'DEACTIVATED', + 'STANDBY', + 'QUOTING', + 'CLOSE_ONLY', + ]).notNullable(); // quoting status of vault + table.timestamp('createdAt').notNullable(); + table.timestamp('updatedAt').notNullable(); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('vaults'); +} diff --git a/indexer/packages/postgres/src/helpers/db-helpers.ts b/indexer/packages/postgres/src/helpers/db-helpers.ts index c1a46abab2..cba32483ba 100644 --- a/indexer/packages/postgres/src/helpers/db-helpers.ts +++ b/indexer/packages/postgres/src/helpers/db-helpers.ts @@ -31,6 +31,7 @@ const layer1Tables = [ 'affiliate_referred_users', 'persistent_cache', 'affiliate_info', + 'vaults', ]; /** diff --git a/indexer/packages/postgres/src/models/vault-model.ts b/indexer/packages/postgres/src/models/vault-model.ts new file mode 100644 index 0000000000..fd7584ca85 --- /dev/null +++ b/indexer/packages/postgres/src/models/vault-model.ts @@ -0,0 +1,44 @@ +import { IntegerPattern } from '../lib/validators'; +import { IsoString, VaultStatus } from '../types'; +import BaseModel from './base-model'; + +export default class VaultModel extends BaseModel { + + static get tableName() { + return 'vaults'; + } + + static get idColumn() { + return ['address']; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'address', + 'clobPairId', + 'status', + 'createdAt', + 'updatedAt', + ], + properties: { + address: { type: 'string' }, + clobPairId: { type: 'string', pattern: IntegerPattern }, + status: { type: 'string' }, + createdAt: { type: 'string', format: 'date-time' }, + updatedAt: { type: 'string', format: 'date-time' }, + }, + }; + } + + address!: string; + + clobPairId!: string; + + status!: VaultStatus; + + createdAt!: IsoString; + + updatedAt!: IsoString; +} diff --git a/indexer/packages/postgres/src/stores/vault-table.ts b/indexer/packages/postgres/src/stores/vault-table.ts new file mode 100644 index 0000000000..4cfe9b6ca8 --- /dev/null +++ b/indexer/packages/postgres/src/stores/vault-table.ts @@ -0,0 +1,100 @@ +import { QueryBuilder } from 'objection'; + +import { DEFAULT_POSTGRES_OPTIONS } from '../constants'; +import { + verifyAllRequiredFields, + setupBaseQuery, +} from '../helpers/stores-helpers'; +import Transaction from '../helpers/transaction'; +import VaultModel from '../models/vault-model'; +import { + QueryConfig, + VaultQueryConfig, + VaultColumns, + Options, + Ordering, + QueryableField, + VaultFromDatabase, + VaultCreateObject, +} from '../types'; + +export async function findAll( + { + address, + clobPairId, + status, + limit, + }: VaultQueryConfig, + requiredFields: QueryableField[], + options: Options = DEFAULT_POSTGRES_OPTIONS, +): Promise { + verifyAllRequiredFields( + { + address, + clobPairId, + status, + limit, + } as QueryConfig, + requiredFields, + ); + + let baseQuery: QueryBuilder = setupBaseQuery( + VaultModel, + options, + ); + + if (address) { + baseQuery = baseQuery.whereIn(VaultColumns.address, address); + } + + if (clobPairId) { + baseQuery = baseQuery.whereIn(VaultColumns.clobPairId, clobPairId); + } + + if (status) { + baseQuery = baseQuery.whereIn(VaultColumns.status, status); + } + + if (options.orderBy !== undefined) { + for (const [column, order] of options.orderBy) { + baseQuery = baseQuery.orderBy( + column, + order, + ); + } + } else { + baseQuery = baseQuery.orderBy( + VaultColumns.clobPairId, + Ordering.ASC, + ); + } + + if (limit) { + baseQuery = baseQuery.limit(limit); + } + + return baseQuery.returning('*'); +} + +export async function create( + vaultToCreate: VaultCreateObject, + options: Options = { txId: undefined }, +): Promise { + return VaultModel.query( + Transaction.get(options.txId), + ).insert({ + ...vaultToCreate, + }); +} + +export async function upsert( + vaultToUpsert: VaultCreateObject, + options: Options = { txId: undefined }, +): Promise { + const vaults: VaultModel[] = await VaultModel.query( + Transaction.get(options.txId), + ).upsert({ + ...vaultToUpsert, + }).returning('*'); + return vaults[0]; +} diff --git a/indexer/packages/postgres/src/types/db-model-types.ts b/indexer/packages/postgres/src/types/db-model-types.ts index 21441874ef..e207b01fad 100644 --- a/indexer/packages/postgres/src/types/db-model-types.ts +++ b/indexer/packages/postgres/src/types/db-model-types.ts @@ -11,6 +11,7 @@ import { PerpetualMarketStatus, PerpetualMarketType } from './perpetual-market-t import { PerpetualPositionStatus } from './perpetual-position-types'; import { PositionSide } from './position-types'; import { TradingRewardAggregationPeriod } from './trading-reward-aggregation-types'; +import { VaultStatus } from './vault-types'; type IsoString = string; @@ -301,6 +302,14 @@ export interface FirebaseNotificationTokenFromDatabase { language: string, } +export interface VaultFromDatabase { + address: string, + clobPairId: string, + status: VaultStatus, + createdAt: IsoString, + updatedAt: IsoString, +} + export type SubaccountAssetNetTransferMap = { [subaccountId: string]: { [assetId: string]: string }, }; export type SubaccountToPerpetualPositionsMap = { [subaccountId: string]: diff --git a/indexer/packages/postgres/src/types/index.ts b/indexer/packages/postgres/src/types/index.ts index c5d6bff805..16c2c1ed9f 100644 --- a/indexer/packages/postgres/src/types/index.ts +++ b/indexer/packages/postgres/src/types/index.ts @@ -32,4 +32,5 @@ export * from './affiliate-referred-users-types'; export * from './persistent-cache-types'; export * from './affiliate-info-types'; export * from './firebase-notification-token-types'; +export * from './vault-types'; export { PositionSide } from './position-types'; diff --git a/indexer/packages/postgres/src/types/query-types.ts b/indexer/packages/postgres/src/types/query-types.ts index d52f90ca12..c5c18cab71 100644 --- a/indexer/packages/postgres/src/types/query-types.ts +++ b/indexer/packages/postgres/src/types/query-types.ts @@ -344,3 +344,9 @@ export interface FirebaseNotificationTokenQueryConfig extends QueryConfig { [QueryableField.TOKEN]?: string, [QueryableField.UPDATED_BEFORE_OR_AT]?: IsoString, } + +export interface VaultQueryConfig extends QueryConfig { + [QueryableField.ADDRESS]?: string[], + [QueryableField.CLOB_PAIR_ID]?: string[], + [QueryableField.STATUS]?: string[], +} diff --git a/indexer/packages/postgres/src/types/vault-types.ts b/indexer/packages/postgres/src/types/vault-types.ts new file mode 100644 index 0000000000..4f1fe3f7a4 --- /dev/null +++ b/indexer/packages/postgres/src/types/vault-types.ts @@ -0,0 +1,24 @@ +import { IsoString } from './utility-types'; + +export interface VaultCreateObject { + address: string, + clobPairId: string, + status: VaultStatus, + createdAt: IsoString, + updatedAt: IsoString, +} + +export enum VaultStatus { + DEACTIVATED = 'DEACTIVATED', + STAND_BY = 'STAND_BY', + QUOTING = 'QUOTING', + CLOSE_ONLY = 'CLOSE_ONLY', +} + +export enum VaultColumns { + address = 'address', + clobPairId = 'clobPairId', + status = 'status', + createdAt = 'createdAt', + updatedAt = 'updatedAt', +} diff --git a/protocol/indexer/events/constants.go b/protocol/indexer/events/constants.go index f32858ae6e..0292aa427e 100644 --- a/protocol/indexer/events/constants.go +++ b/protocol/indexer/events/constants.go @@ -20,6 +20,7 @@ const ( SubtypeTradingReward = "trading_reward" SubtypeOpenInterestUpdate = "open_interest_update" SubtypeRegisterAffiliate = "register_affiliate" + SubtypeUpsertVault = "upsert_vault" ) const ( @@ -39,6 +40,7 @@ const ( TradingRewardVersion uint32 = 1 OpenInterestUpdateVersion uint32 = 1 RegisterAffiliateEventVersion uint32 = 1 + UpsertVaultEventVersion uint32 = 1 ) var OnChainEventSubtypes = []string{ @@ -56,4 +58,5 @@ var OnChainEventSubtypes = []string{ SubtypeDeleveraging, SubtypeTradingReward, SubtypeRegisterAffiliate, + SubtypeUpsertVault, }