From b82426a5d1d16ad726b20a2a09ab2062292ee731 Mon Sep 17 00:00:00 2001 From: Matt <90358481+xbtmatt@users.noreply.github.com> Date: Thu, 30 Jan 2025 16:28:41 -0800 Subject: [PATCH] [ECO-2709] Add aggregate market state script (#527) --- package.json | 3 +- src/typescript/package.json | 3 +- src/typescript/sdk/package.json | 3 +- .../sdk/src/client/emojicoin-client.ts | 22 +++++- .../queries/fetch-aggregate-market-state.ts | 20 ++++++ .../sdk/src/indexer-v2/types/index.ts | 25 +++++++ .../sdk/src/indexer-v2/types/json-types.ts | 26 ++++++- .../sdk/src/scripts/verify-processor-data.ts | 68 +++++++++++++++++++ src/typescript/sdk/src/types/json-types.ts | 2 +- src/typescript/sdk/tests/e2e/schema.test.ts | 1 + 10 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/typescript/sdk/src/indexer-v2/queries/fetch-aggregate-market-state.ts create mode 100644 src/typescript/sdk/src/scripts/verify-processor-data.ts diff --git a/package.json b/package.json index fd6d96bb7..f86701de1 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "test:sdk": "pnpm --prefix src/typescript run test:sdk", "test:sdk:e2e": "pnpm --prefix src/typescript run test:sdk:e2e", "test:sdk:unit": "pnpm --prefix src/typescript run test:sdk:unit", - "test:verbose": "pnpm --prefix src/typescript run test:verbose" + "test:verbose": "pnpm --prefix src/typescript run test:verbose", + "verify-processor-data": "pnpm --prefix src/typescript/sdk run verify-processor-data" } } diff --git a/src/typescript/package.json b/src/typescript/package.json index 1775fdeb2..eb7bb78cb 100644 --- a/src/typescript/package.json +++ b/src/typescript/package.json @@ -49,7 +49,8 @@ "test:sdk:sequential": "pnpm run load-env:test -- turbo run test:sdk:sequential --filter @econia-labs/emojicoin-sdk --force --log-prefix none", "test:sdk:unit": "NO_TEST_SETUP=true pnpm run load-env:test -- turbo run test:unit --filter @econia-labs/emojicoin-sdk --force --log-prefix none", "test:unit": "NO_TEST_SETUP=true pnpm run load-env:test -- turbo run test:unit --force --log-prefix none --ui tui", - "test:verbose": "FETCH_DEBUG=true VERBOSE_TEST_LOGS=true pnpm run load-env:test -- turbo run test --force" + "test:verbose": "FETCH_DEBUG=true VERBOSE_TEST_LOGS=true pnpm run load-env:test -- turbo run test --force", + "verify-processor-data": "pnpm --prefix sdk run verify-processor-data" }, "version": "0.0.0", "workspaces": [ diff --git a/src/typescript/sdk/package.json b/src/typescript/sdk/package.json index 32952b151..1a7280801 100644 --- a/src/typescript/sdk/package.json +++ b/src/typescript/sdk/package.json @@ -78,7 +78,8 @@ "test:e2e": "pnpm run test:sdk:parallel && pnpm run test:sdk:sequential", "test:sdk:parallel": "pnpm jest --testPathIgnorePatterns=tests/e2e/broker", "test:sdk:sequential": "pnpm jest --runInBand tests/e2e/broker", - "test:unit": "pnpm jest tests/unit" + "test:unit": "pnpm jest tests/unit", + "verify-processor-data": "pnpm dotenv -e ../.env -- pnpm tsx --conditions=react-server src/scripts/verify-processor-data.ts" }, "typings": "dist/index.d.ts", "version": "0.3.0" diff --git a/src/typescript/sdk/src/client/emojicoin-client.ts b/src/typescript/sdk/src/client/emojicoin-client.ts index 376e11ee3..a1f946ba5 100644 --- a/src/typescript/sdk/src/client/emojicoin-client.ts +++ b/src/typescript/sdk/src/client/emojicoin-client.ts @@ -8,6 +8,7 @@ import { type InputGenerateTransactionOptions, type WaitForTransactionOptions, AptosConfig, + type LedgerVersionArg, } from "@aptos-labs/ts-sdk"; import { type ChatEmoji, type SymbolEmoji } from "../emoji_data/types"; import { EmojicoinDotFun, getEvents } from "../emojicoin_dot_fun"; @@ -29,7 +30,7 @@ import { DEFAULT_REGISTER_MARKET_GAS_OPTIONS, INTEGRATOR_ADDRESS } from "../cons import { waitFor } from "../utils"; import { postgrest } from "../indexer-v2/queries"; import { TableName } from "../indexer-v2/types/json-types"; -import { toSwapEvent, type AnyNumberString } from "../types"; +import { toMarketView, toRegistryView, toSwapEvent, type AnyNumberString } from "../types"; const { expect, Expect } = customExpect; @@ -135,10 +136,14 @@ export class EmojicoinClient { marketExists: typeof EmojicoinClient.prototype.isMarketRegisteredView; simulateBuy: typeof EmojicoinClient.prototype.simulateBuy; simulateSell: typeof EmojicoinClient.prototype.simulateSell; + registry: typeof EmojicoinClient.prototype.registryView; + market: typeof EmojicoinClient.prototype.marketView; } = { marketExists: this.isMarketRegisteredView.bind(this), simulateBuy: this.simulateBuy.bind(this), simulateSell: this.simulateSell.bind(this), + registry: this.registryView.bind(this), + market: this.marketView.bind(this), }; private integrator: AccountAddress; @@ -338,6 +343,21 @@ export class EmojicoinClient { return typeof res.vec.pop() !== "undefined"; } + private async registryView(options?: LedgerVersionArg) { + return await EmojicoinDotFun.RegistryView.view({ + aptos: this.aptos, + options, + }).then(toRegistryView); + } + + private async marketView(marketAddress: AccountAddressInput, options?: LedgerVersionArg) { + return await EmojicoinDotFun.MarketView.view({ + marketAddress, + aptos: this.aptos, + options, + }).then(toMarketView); + } + private async swap( swapper: Account, symbolEmojis: SymbolEmoji[], diff --git a/src/typescript/sdk/src/indexer-v2/queries/fetch-aggregate-market-state.ts b/src/typescript/sdk/src/indexer-v2/queries/fetch-aggregate-market-state.ts new file mode 100644 index 000000000..456c089f0 --- /dev/null +++ b/src/typescript/sdk/src/indexer-v2/queries/fetch-aggregate-market-state.ts @@ -0,0 +1,20 @@ +import { toAggregateMarketState, type DatabaseJsonType } from "../types"; +import { postgrest } from "./client"; + +export const callAggregateMarketState = () => + // Since this is a read-only function call, prefer to call this as a `GET` request. It makes API + // gateway authorization simpler and cleaner. + postgrest + .rpc("aggregate_market_state", {}, { get: true }) + .select("*") + .single() + .then((res) => { + if (res.data) { + return res.data as DatabaseJsonType["aggregate_market_state"]; + } + if (res.error) { + console.error(res.error); + } + throw new Error("RPC call to `aggregate_market_state` failed, `null` was returned."); + }) + .then(toAggregateMarketState); diff --git a/src/typescript/sdk/src/indexer-v2/types/index.ts b/src/typescript/sdk/src/indexer-v2/types/index.ts index 1f720e5a6..ece8ecf28 100644 --- a/src/typescript/sdk/src/indexer-v2/types/index.ts +++ b/src/typescript/sdk/src/indexer-v2/types/index.ts @@ -441,6 +441,7 @@ export type ArenaLeaderboardModel = ReturnType; export type ArenaLeaderboardHistoryModel = ReturnType; export type ArenaInfoModel = ReturnType; export type UserPoolsRPCModel = ReturnType; +export type AggregateMarketStateModel = ReturnType; /** * Converts a function that converts a type to another type into a function that converts the type @@ -771,6 +772,28 @@ export const toPriceFeed = (data: DatabaseJsonType["price_feed"]) => { }; }; +export const toAggregateMarketState = (data: DatabaseJsonType["aggregate_market_state"]) => ({ + lastEmojicoinTransactionVersion: BigInt(data.last_emojicoin_transaction_version), + cumulativeChatMessages: BigInt(data.cumulative_chat_messages), + cumulativeIntegratorFees: BigInt(data.cumulative_integrator_fees), + cumulativeQuoteVolume: BigInt(data.cumulative_quote_volume), + cumulativeSwaps: BigInt(data.cumulative_swaps), + fullyDilutedValue: BigInt(data.fully_diluted_value), + lastBumpTime: postgresTimestampToMicroseconds(data.last_bump_time), + marketCap: BigInt(data.market_cap), + numMarkets: BigInt(data.n_markets), + nonce: BigInt(data.nonce), + totalQuoteLocked: BigInt(data.total_quote_locked), + totalValueLocked: BigInt(data.total_value_locked), + numMarketsInBondingCurve: BigInt(data.n_markets_in_bonding_curve), + numMarketsPostBondingCurve: BigInt(data.n_markets_post_bonding_curve), + numGlobalStateEvents: BigInt(data.n_global_state_events), + numMarketRegistrationEvents: BigInt(data.n_market_registration_events), + numSwapEvents: BigInt(data.n_swap_events), + numChatEvents: BigInt(data.n_chat_events), + numLiquidityEvents: BigInt(data.n_liquidity_events), +}); + export const DatabaseTypeConverter = { [TableName.GlobalStateEvents]: toGlobalStateEventModel, [TableName.PeriodicStateEvents]: toPeriodicStateEventModel, @@ -795,6 +818,7 @@ export const DatabaseTypeConverter = { [TableName.ArenaLeaderboard]: toArenaLeaderboardModel, [TableName.ArenaLeaderboardHistory]: toArenaLeaderboardHistoryModel, [DatabaseRpc.UserPools]: toUserPoolsRPCResponse, + [DatabaseRpc.AggregateMarketState]: toAggregateMarketState, }; export type DatabaseModels = { @@ -821,6 +845,7 @@ export type DatabaseModels = { [TableName.ArenaLeaderboard]: ArenaLeaderboardModel; [TableName.ArenaLeaderboardHistory]: ArenaLeaderboardHistoryModel; [DatabaseRpc.UserPools]: UserPoolsRPCModel; + [DatabaseRpc.AggregateMarketState]: AggregateMarketStateModel; }; export type AnyEventTable = diff --git a/src/typescript/sdk/src/indexer-v2/types/json-types.ts b/src/typescript/sdk/src/indexer-v2/types/json-types.ts index 92d4ac49a..c61c94bfa 100644 --- a/src/typescript/sdk/src/indexer-v2/types/json-types.ts +++ b/src/typescript/sdk/src/indexer-v2/types/json-types.ts @@ -413,7 +413,7 @@ export enum TableName { export enum DatabaseRpc { UserPools = "user_pools", - PriceFeed = "price_feed", + AggregateMarketState = "aggregate_market_state", } // Fields that only exist after being processed by a processor. @@ -514,6 +514,27 @@ export type DatabaseJsonType = { ProcessedFields & UserLPCoinBalance & { daily_volume: Uint128String } >; + [DatabaseRpc.AggregateMarketState]: Flatten<{ + last_emojicoin_transaction_version: Uint64String; + cumulative_chat_messages: Uint64String; + cumulative_integrator_fees: Uint128String; + cumulative_quote_volume: Uint128String; + cumulative_swaps: Uint64String; + fully_diluted_value: Uint128String; + last_bump_time: PostgresTimestamp; + market_cap: Uint128String; + n_markets: Uint64String; + nonce: Uint64String; + total_quote_locked: Uint128String; + total_value_locked: Uint128String; + n_markets_in_bonding_curve: Uint64String; + n_markets_post_bonding_curve: Uint64String; + n_global_state_events: Uint64String; + n_market_registration_events: Uint64String; + n_swap_events: Uint64String; + n_chat_events: Uint64String; + n_liquidity_events: Uint64String; + }>; }; type Columns = DatabaseJsonType[TableName.GlobalStateEvents] & @@ -538,6 +559,7 @@ type Columns = DatabaseJsonType[TableName.GlobalStateEvents] & DatabaseJsonType[TableName.ArenaInfo] & DatabaseJsonType[TableName.ArenaLeaderboard] & DatabaseJsonType[TableName.ArenaLeaderboardHistory] & - DatabaseJsonType[DatabaseRpc.UserPools]; + DatabaseJsonType[DatabaseRpc.UserPools] & + DatabaseJsonType[DatabaseRpc.AggregateMarketState]; export type AnyColumnName = keyof Columns; diff --git a/src/typescript/sdk/src/scripts/verify-processor-data.ts b/src/typescript/sdk/src/scripts/verify-processor-data.ts new file mode 100644 index 000000000..95498c447 --- /dev/null +++ b/src/typescript/sdk/src/scripts/verify-processor-data.ts @@ -0,0 +1,68 @@ +/* eslint-disable import/no-unused-modules */ +import { EmojicoinClient } from "../client/emojicoin-client"; +import { callAggregateMarketState } from "../indexer-v2/queries/fetch-aggregate-market-state"; + +const main = async () => { + const aggregateMarketState = await callAggregateMarketState(); + const emojicoin = new EmojicoinClient(); + const registryState = await emojicoin.view.registry({ + ledgerVersion: aggregateMarketState.lastEmojicoinTransactionVersion, + }); + + const totalNumDatabaseEvents = BigInt( + aggregateMarketState.numGlobalStateEvents + + aggregateMarketState.numSwapEvents + + aggregateMarketState.numLiquidityEvents + + aggregateMarketState.numChatEvents + + aggregateMarketState.numMarketRegistrationEvents + ); + + if (!(registryState.nonce !== totalNumDatabaseEvents)) { + console.error(`Registry state nonce doesn't match total number of database events.`); + console.error( + `Registry nonce: ${registryState.nonce}, numDatabaseEvents: ${totalNumDatabaseEvents})` + ); + } + + const equalValues = Object.entries(aggregateMarketState) + .map(([key, aggValue]) => { + // Skip these keys as they are checked above or not checked at all and only for viewing. + if ( + [ + "lastEmojicoinTransactionVersion", + // The lastBumpTime in the registry view is NOT the same as the last event bump time. + // It's the last bump time of the global registry (once per day). + // The lastBumpTime in the aggregated market state *is* the last event bump time. + // So they'll never be equal- thus, ignore. + "lastBumpTime", + "numMarketsInBondingCurve", // Only for debugging- not checked. + "numMarketsPostBondingCurve", // Only for debugging- not checked. + "numGlobalStateEvents", + "numMarketRegistrationEvents", + "numSwapEvents", + "numChatEvents", + "numLiquidityEvents", + ].includes(key) + ) { + return true; + } + const viewValue = registryState[key as keyof typeof registryState]; + const equal = aggValue === viewValue; + if (!equal) { + console.error(`Key ${key} not equal. Database: ${aggValue} View: ${viewValue}`); + } + return equal; + }) + .every((v) => v); + if (!equalValues) { + console.error(aggregateMarketState); + console.error(registryState); + throw new Error("Aggregate market state does not match the registry view on-chain."); + } + /* eslint-disable no-console */ + console.info("Aggregate market state matches the registry view on-chain!"); + console.dir(aggregateMarketState); + /* eslint-enable no-console */ +}; + +main(); diff --git a/src/typescript/sdk/src/types/json-types.ts b/src/typescript/sdk/src/types/json-types.ts index 0a320f2b2..64f563cb8 100644 --- a/src/typescript/sdk/src/types/json-types.ts +++ b/src/typescript/sdk/src/types/json-types.ts @@ -89,7 +89,7 @@ type JsonTypes = { fully_diluted_value: AggregatorSnapshot; cumulative_integrator_fees: AggregatorSnapshot; cumulative_swaps: AggregatorSnapshot; - cumulative_chat_messages: AggregatorSnapshot; + cumulative_chat_messages: AggregatorSnapshot; }; // The result of the contract's `market_view` view function. NOT the database view. diff --git a/src/typescript/sdk/tests/e2e/schema.test.ts b/src/typescript/sdk/tests/e2e/schema.test.ts index bac608651..a7d69b5ca 100644 --- a/src/typescript/sdk/tests/e2e/schema.test.ts +++ b/src/typescript/sdk/tests/e2e/schema.test.ts @@ -8,6 +8,7 @@ import { } from "../../src/indexer-v2/types/postgres-numeric-types"; // This is not the full response type; it's just what we use in this test. +// NOTE: This does *not* cover the RPC function calls/schemas. Only tables and views. interface DatabaseSchema { definitions: { [Table in TableName]: {