diff --git a/.github/workflows/indexer-build-and-push-dev-staging.yml b/.github/workflows/indexer-build-and-push-dev-staging.yml index 5a98b72552..62cd8f267f 100644 --- a/.github/workflows/indexer-build-and-push-dev-staging.yml +++ b/.github/workflows/indexer-build-and-push-dev-staging.yml @@ -3,56 +3,16 @@ name: Indexer Build & Push Images to AWS ECR for Dev / Staging branches on: # yamllint disable-line rule:truthy push: branches: + - adam/ct-948-add-functionality-to-auxo-to-create-new-kafka-topics - main - 'release/indexer/v[0-9]+.[0-9]+.x' # e.g. release/indexer/v0.1.x - 'release/indexer/v[0-9]+.x' # e.g. release/indexer/v1.x # TODO(DEC-837): Customize github build and push to ECR by service with paths jobs: - # Build and push to dev - call-build-and-push-ecs-services-dev: - name: (Dev) Build and Push ECS Services - uses: ./.github/workflows/indexer-build-and-push-all-ecr-images.yml - with: - ENVIRONMENT: dev - secrets: inherit - - # Build and push to dev2 - call-build-and-push-ecs-services-dev2: - name: (Dev2) Build and Push ECS Services - uses: ./.github/workflows/indexer-build-and-push-all-ecr-images.yml - with: - ENVIRONMENT: dev2 - secrets: inherit - - # Build and push to dev3 - call-build-and-push-ecs-services-dev3: - name: (Dev3) Build and Push ECS Services - uses: ./.github/workflows/indexer-build-and-push-all-ecr-images.yml - with: - ENVIRONMENT: dev3 - secrets: inherit - - # Build and push to dev4 - call-build-and-push-ecs-services-dev4: - name: (Dev4) Build and Push ECS Services - uses: ./.github/workflows/indexer-build-and-push-all-ecr-images.yml - with: - ENVIRONMENT: dev4 - secrets: inherit - - # Build and push to dev5 call-build-and-push-ecs-services-dev5: name: (Dev5) Build and Push ECS Services uses: ./.github/workflows/indexer-build-and-push-all-ecr-images.yml with: ENVIRONMENT: dev5 - secrets: inherit - - # Build and push to staging - call-build-and-push-ecs-services-staging: - name: (Staging) Build and Push ECS Services - uses: ./.github/workflows/indexer-build-and-push-all-ecr-images.yml - with: - ENVIRONMENT: staging - secrets: inherit + secrets: inherit \ No newline at end of file diff --git a/indexer/README.md b/indexer/README.md index 26fab47508..7598e2c9d9 100644 --- a/indexer/README.md +++ b/indexer/README.md @@ -230,4 +230,5 @@ Other example subscription events: { "type": "subscribe", "channel": "v4_markets" } { "type": "subscribe", "channel": "v4_orderbook", "id": "BTC-USD" } { "type": "subscribe", "channel": "v4_subaccounts", "id": "address/0" } +{ "type": "subscribe", "channel": "v4_block_height" } ``` diff --git a/indexer/docker-compose-local-deployment.yml b/indexer/docker-compose-local-deployment.yml index 832c27fa85..075ee7a4d5 100644 --- a/indexer/docker-compose-local-deployment.yml +++ b/indexer/docker-compose-local-deployment.yml @@ -13,7 +13,8 @@ services: to-websockets-subaccounts:1:1,\ to-websockets-trades:1:1,\ to-websockets-markets:1:1,\ - to-websockets-candles:1:1" + to-websockets-candles:1:1,\ + to-websockets-block-height:1:1" KAFKA_LISTENERS: INTERNAL://:9092,EXTERNAL_SAME_HOST://:29092 KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,EXTERNAL_SAME_HOST://localhost:29092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL_SAME_HOST:PLAINTEXT diff --git a/indexer/docker-compose.yml b/indexer/docker-compose.yml index 141505046e..306469544b 100644 --- a/indexer/docker-compose.yml +++ b/indexer/docker-compose.yml @@ -7,14 +7,15 @@ services: environment: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_ADVERTISED_HOST_NAME: localhost - KAFKA_CREATE_TOPICS: + KAFKA_CREATE_TOPICS: "to-ender:1:1,\ to-vulcan:1:1,\ to-websockets-orderbooks:1:1,\ to-websockets-subaccounts:1:1,\ to-websockets-trades:1:1,\ to-websockets-markets:1:1,\ - to-websockets-candles:1:1" + to-websockets-candles:1:1,\ + to-websockets-block-height:1:1" postgres-test: build: context: . diff --git a/indexer/packages/kafka/src/constants.ts b/indexer/packages/kafka/src/constants.ts index 9a02cb1b3e..01b5a3712b 100644 --- a/indexer/packages/kafka/src/constants.ts +++ b/indexer/packages/kafka/src/constants.ts @@ -5,3 +5,4 @@ export const SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION: string = '3.0.0'; export const TRADES_WEBSOCKET_MESSAGE_VERSION: string = '2.1.0'; export const MARKETS_WEBSOCKET_MESSAGE_VERSION: string = '1.0.0'; export const CANDLES_WEBSOCKET_MESSAGE_VERSION: string = '1.0.0'; +export const BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION: string = '1.0.0'; diff --git a/indexer/packages/kafka/src/types.ts b/indexer/packages/kafka/src/types.ts index d18c465995..27b0b3e993 100644 --- a/indexer/packages/kafka/src/types.ts +++ b/indexer/packages/kafka/src/types.ts @@ -4,6 +4,7 @@ export enum WebsocketTopics { TO_WEBSOCKETS_TRADES = 'to-websockets-trades', TO_WEBSOCKETS_MARKETS = 'to-websockets-markets', TO_WEBSOCKETS_CANDLES = 'to-websockets-candles', + TO_WEBSOCKETS_BLOCK_HEIGHT = 'to-websockets-block-height', } export enum KafkaTopics { @@ -14,4 +15,5 @@ export enum KafkaTopics { TO_WEBSOCKETS_TRADES = 'to-websockets-trades', TO_WEBSOCKETS_MARKETS = 'to-websockets-markets', TO_WEBSOCKETS_CANDLES = 'to-websockets-candles', + TO_WEBSOCKETS_BLOCK_HEIGHT = 'to-websockets-block-height', } diff --git a/indexer/packages/postgres/src/db/migrations/migration_files/20240613174832_add_gb_to_compliance_reasons.ts b/indexer/packages/postgres/src/db/migrations/migration_files/20240613174832_add_gb_to_compliance_reasons.ts new file mode 100644 index 0000000000..c83af715f3 --- /dev/null +++ b/indexer/packages/postgres/src/db/migrations/migration_files/20240613174832_add_gb_to_compliance_reasons.ts @@ -0,0 +1,19 @@ +import * as Knex from 'knex'; + +import { formatAlterTableEnumSql } from '../helpers'; + +export async function up(knex: Knex): Promise { + return knex.raw(formatAlterTableEnumSql( + 'compliance_status', + 'reason', + ['MANUAL', 'US_GEO', 'CA_GEO', 'GB_GEO', 'SANCTIONED_GEO', 'COMPLIANCE_PROVIDER'], + )); +} + +export async function down(knex: Knex): Promise { + return knex.raw(formatAlterTableEnumSql( + 'compliance_status', + 'reason', + ['MANUAL', 'US_GEO', 'CA_GEO', 'SANCTIONED_GEO', 'COMPLIANCE_PROVIDER'], + )); +} diff --git a/indexer/packages/postgres/src/types/compliance-status-types.ts b/indexer/packages/postgres/src/types/compliance-status-types.ts index 2c2927da2f..e5099ff073 100644 --- a/indexer/packages/postgres/src/types/compliance-status-types.ts +++ b/indexer/packages/postgres/src/types/compliance-status-types.ts @@ -6,6 +6,7 @@ export enum ComplianceReason { MANUAL = 'MANUAL', US_GEO = 'US_GEO', CA_GEO = 'CA_GEO', + GB_GEO = 'GB_GEO', SANCTIONED_GEO = 'SANCTIONED_GEO', COMPLIANCE_PROVIDER = 'COMPLIANCE_PROVIDER', } diff --git a/indexer/packages/redis/__tests__/caches/orderbook-levels-cache.test.ts b/indexer/packages/redis/__tests__/caches/orderbook-levels-cache.test.ts index e4d601e098..af077cf171 100644 --- a/indexer/packages/redis/__tests__/caches/orderbook-levels-cache.test.ts +++ b/indexer/packages/redis/__tests__/caches/orderbook-levels-cache.test.ts @@ -15,7 +15,7 @@ import { } from '../../src/caches/orderbook-levels-cache'; import { OrderSide } from '@dydxprotocol-indexer/postgres'; import { OrderbookLevels, PriceLevel } from '../../src/types'; -import { InvalidOptionsError, InvalidPriceLevelUpdateError } from '../../src/errors'; +import { InvalidOptionsError } from '../../src/errors'; import { logger } from '@dydxprotocol-indexer/base'; describe('orderbookLevelsCache', () => { @@ -176,11 +176,10 @@ describe('orderbookLevelsCache', () => { expect(orderbookLevels.bids).toEqual([]); }); - it('throws error if update will cause quantums to be negative', async () => { + it('sets price level to 0 if update will cause quantums to be negative', async () => { const humanPrice: string = '50000'; const quantums: string = '1000'; const invalidDelta: string = '-2000'; - const resultingQuantums: string = '-1000'; // Set existing quantums for the level await updatePriceLevel({ ticker, @@ -190,31 +189,26 @@ describe('orderbookLevelsCache', () => { client, }); - // Test that an error is thrown if the update results in a negative quantums for the price - // level - await expect(updatePriceLevel({ + await updatePriceLevel({ ticker, side: OrderSide.BUY, humanPrice, sizeDeltaInQuantums: invalidDelta, client, - })).rejects.toBeInstanceOf(InvalidPriceLevelUpdateError); + }); expect(logger.crit).toHaveBeenCalledTimes(1); - await expect(updatePriceLevel({ + + // Expect that the value in the orderbook is set to 0 + const orderbookLevels: OrderbookLevels = await getOrderBookLevels( ticker, - side: OrderSide.BUY, - humanPrice, - sizeDeltaInQuantums: invalidDelta, client, - })).rejects.toEqual(expect.objectContaining({ - message: expect.stringContaining(resultingQuantums), - })); - - // Expect that the value in the orderbook is unchanged - const orderbookLevels: OrderbookLevels = await getOrderBookLevels(ticker, client); + { + removeZeros: false, + }, + ); expect(orderbookLevels.bids).toMatchObject([{ humanPrice, - quantums, + quantums: '0', }]); }); }); diff --git a/indexer/packages/redis/src/caches/orderbook-levels-cache.ts b/indexer/packages/redis/src/caches/orderbook-levels-cache.ts index 3dc30b040a..f094d80d7b 100644 --- a/indexer/packages/redis/src/caches/orderbook-levels-cache.ts +++ b/indexer/packages/redis/src/caches/orderbook-levels-cache.ts @@ -4,7 +4,7 @@ import Big from 'big.js'; import _ from 'lodash'; import { Callback, RedisClient } from 'redis'; -import { InvalidOptionsError, InvalidPriceLevelUpdateError } from '../errors'; +import { InvalidOptionsError } from '../errors'; import { hGetAsync } from '../helpers/redis'; import { OrderbookLevels, PriceLevel } from '../types'; import { @@ -58,32 +58,29 @@ export async function updatePriceLevel({ // NOTE: If this happens from a single price level update, it's possible for multiple subsequent // price level updates to fail with the same error due to interleaved price level updates. if (updatedQuantums < 0) { - // Undo the update. This can't be done in a Lua script as Redis runs Lua 5.1, which only - // uses doubles which support up to 53-bit integers. Race-condition where it's possible for a - // price-level to have negative quantums handled in `getOrderbookLevels` where price-levels with - // negative quantums are filtered out. Note: even though we are reverting this information, each - // call to incrementOrderbookLevel updates the lastUpdated key in the cache. + // Set the price level to 0. + // Race-condition where it's possible for a price-level to have negative quantums handled in + // `getOrderbookLevels` where price-levels with negative quantums are filtered out. Note: even + // though we are reverting this information, each call to incrementOrderbookLevel updates the + // lastUpdated key in the cache. await incrementOrderbookLevel( ticker, side, humanPrice, // Needs to be an integer - Big(sizeDeltaInQuantums).mul(-1).toFixed(0), + Big(updatedQuantums).mul(-1).toFixed(0), client, ); logger.crit({ at: 'orderbookLevelsCache#updatePriceLevel', - message: 'Price level updated to negative quantums', + message: 'Price level updated to negative quantums, set to zero', ticker, side, humanPrice, updatedQuantums, sizeDeltaInQuantums, }); - throw new InvalidPriceLevelUpdateError( - '#updatePriceLevel: Resulting price level has negative quantums, quantums = ' + - `${updatedQuantums}`, - ); + return 0; } return updatedQuantums; diff --git a/indexer/packages/redis/src/errors.ts b/indexer/packages/redis/src/errors.ts index 55ca29bb34..84bd260308 100644 --- a/indexer/packages/redis/src/errors.ts +++ b/indexer/packages/redis/src/errors.ts @@ -21,11 +21,3 @@ export class InvalidOptionsError extends Error { Error.captureStackTrace(this, this.constructor); } } - -export class InvalidPriceLevelUpdateError extends Error { - constructor(message: string) { - super(`Invalid price level update: ${message}`); - this.name = this.constructor.name; - Error.captureStackTrace(this, this.constructor); - } -} diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts index ebb2583e3c..2685a389a5 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts @@ -73,100 +73,109 @@ import * as _76 from "./ratelimit/limit_params"; import * as _77 from "./ratelimit/pending_send_packet"; import * as _78 from "./ratelimit/query"; import * as _79 from "./ratelimit/tx"; -import * as _80 from "./rewards/genesis"; -import * as _81 from "./rewards/params"; -import * as _82 from "./rewards/query"; -import * as _83 from "./rewards/reward_share"; -import * as _84 from "./rewards/tx"; -import * as _85 from "./sending/genesis"; -import * as _86 from "./sending/query"; -import * as _87 from "./sending/transfer"; -import * as _88 from "./sending/tx"; -import * as _89 from "./stats/genesis"; -import * as _90 from "./stats/params"; -import * as _91 from "./stats/query"; -import * as _92 from "./stats/stats"; -import * as _93 from "./stats/tx"; -import * as _94 from "./subaccounts/asset_position"; -import * as _95 from "./subaccounts/genesis"; -import * as _96 from "./subaccounts/perpetual_position"; -import * as _97 from "./subaccounts/query"; -import * as _98 from "./subaccounts/subaccount"; -import * as _99 from "./vault/genesis"; -import * as _100 from "./vault/params"; -import * as _101 from "./vault/query"; -import * as _102 from "./vault/tx"; -import * as _103 from "./vault/vault"; -import * as _104 from "./vest/genesis"; -import * as _105 from "./vest/query"; -import * as _106 from "./vest/tx"; -import * as _107 from "./vest/vest_entry"; -import * as _115 from "./assets/query.lcd"; -import * as _116 from "./blocktime/query.lcd"; -import * as _117 from "./bridge/query.lcd"; -import * as _118 from "./clob/query.lcd"; -import * as _119 from "./delaymsg/query.lcd"; -import * as _120 from "./epochs/query.lcd"; -import * as _121 from "./feetiers/query.lcd"; -import * as _122 from "./perpetuals/query.lcd"; -import * as _123 from "./prices/query.lcd"; -import * as _124 from "./ratelimit/query.lcd"; -import * as _125 from "./rewards/query.lcd"; -import * as _126 from "./stats/query.lcd"; -import * as _127 from "./subaccounts/query.lcd"; -import * as _128 from "./vault/query.lcd"; -import * as _129 from "./vest/query.lcd"; -import * as _130 from "./assets/query.rpc.Query"; -import * as _131 from "./blocktime/query.rpc.Query"; -import * as _132 from "./bridge/query.rpc.Query"; -import * as _133 from "./clob/query.rpc.Query"; -import * as _134 from "./delaymsg/query.rpc.Query"; -import * as _135 from "./epochs/query.rpc.Query"; -import * as _136 from "./feetiers/query.rpc.Query"; -import * as _137 from "./govplus/query.rpc.Query"; -import * as _138 from "./listing/query.rpc.Query"; -import * as _139 from "./perpetuals/query.rpc.Query"; -import * as _140 from "./prices/query.rpc.Query"; -import * as _141 from "./ratelimit/query.rpc.Query"; -import * as _142 from "./rewards/query.rpc.Query"; -import * as _143 from "./sending/query.rpc.Query"; -import * as _144 from "./stats/query.rpc.Query"; -import * as _145 from "./subaccounts/query.rpc.Query"; -import * as _146 from "./vault/query.rpc.Query"; -import * as _147 from "./vest/query.rpc.Query"; -import * as _148 from "./blocktime/tx.rpc.msg"; -import * as _149 from "./bridge/tx.rpc.msg"; -import * as _150 from "./clob/tx.rpc.msg"; -import * as _151 from "./delaymsg/tx.rpc.msg"; -import * as _152 from "./feetiers/tx.rpc.msg"; -import * as _153 from "./govplus/tx.rpc.msg"; -import * as _154 from "./perpetuals/tx.rpc.msg"; -import * as _155 from "./prices/tx.rpc.msg"; -import * as _156 from "./ratelimit/tx.rpc.msg"; -import * as _157 from "./rewards/tx.rpc.msg"; -import * as _158 from "./sending/tx.rpc.msg"; -import * as _159 from "./stats/tx.rpc.msg"; -import * as _160 from "./vault/tx.rpc.msg"; -import * as _161 from "./vest/tx.rpc.msg"; -import * as _162 from "./lcd"; -import * as _163 from "./rpc.query"; -import * as _164 from "./rpc.tx"; +import * as _80 from "./revshare/genesis"; +import * as _81 from "./revshare/params"; +import * as _82 from "./revshare/query"; +import * as _83 from "./revshare/revshare"; +import * as _84 from "./revshare/tx"; +import * as _85 from "./rewards/genesis"; +import * as _86 from "./rewards/params"; +import * as _87 from "./rewards/query"; +import * as _88 from "./rewards/reward_share"; +import * as _89 from "./rewards/tx"; +import * as _90 from "./sending/genesis"; +import * as _91 from "./sending/query"; +import * as _92 from "./sending/transfer"; +import * as _93 from "./sending/tx"; +import * as _94 from "./stats/genesis"; +import * as _95 from "./stats/params"; +import * as _96 from "./stats/query"; +import * as _97 from "./stats/stats"; +import * as _98 from "./stats/tx"; +import * as _99 from "./subaccounts/asset_position"; +import * as _100 from "./subaccounts/genesis"; +import * as _101 from "./subaccounts/perpetual_position"; +import * as _102 from "./subaccounts/query"; +import * as _103 from "./subaccounts/subaccount"; +import * as _104 from "./vault/genesis"; +import * as _105 from "./vault/params"; +import * as _106 from "./vault/query"; +import * as _107 from "./vault/tx"; +import * as _108 from "./vault/vault"; +import * as _109 from "./vest/genesis"; +import * as _110 from "./vest/query"; +import * as _111 from "./vest/tx"; +import * as _112 from "./vest/vest_entry"; +import * as _120 from "./assets/query.lcd"; +import * as _121 from "./blocktime/query.lcd"; +import * as _122 from "./bridge/query.lcd"; +import * as _123 from "./clob/query.lcd"; +import * as _124 from "./delaymsg/query.lcd"; +import * as _125 from "./epochs/query.lcd"; +import * as _126 from "./feetiers/query.lcd"; +import * as _127 from "./perpetuals/query.lcd"; +import * as _128 from "./prices/query.lcd"; +import * as _129 from "./ratelimit/query.lcd"; +import * as _130 from "./revshare/query.lcd"; +import * as _131 from "./rewards/query.lcd"; +import * as _132 from "./stats/query.lcd"; +import * as _133 from "./subaccounts/query.lcd"; +import * as _134 from "./vault/query.lcd"; +import * as _135 from "./vest/query.lcd"; +import * as _136 from "./assets/query.rpc.Query"; +import * as _137 from "./blocktime/query.rpc.Query"; +import * as _138 from "./bridge/query.rpc.Query"; +import * as _139 from "./clob/query.rpc.Query"; +import * as _140 from "./delaymsg/query.rpc.Query"; +import * as _141 from "./epochs/query.rpc.Query"; +import * as _142 from "./feetiers/query.rpc.Query"; +import * as _143 from "./govplus/query.rpc.Query"; +import * as _144 from "./listing/query.rpc.Query"; +import * as _145 from "./perpetuals/query.rpc.Query"; +import * as _146 from "./prices/query.rpc.Query"; +import * as _147 from "./ratelimit/query.rpc.Query"; +import * as _148 from "./revshare/query.rpc.Query"; +import * as _149 from "./rewards/query.rpc.Query"; +import * as _150 from "./sending/query.rpc.Query"; +import * as _151 from "./stats/query.rpc.Query"; +import * as _152 from "./subaccounts/query.rpc.Query"; +import * as _153 from "./vault/query.rpc.Query"; +import * as _154 from "./vest/query.rpc.Query"; +import * as _155 from "./blocktime/tx.rpc.msg"; +import * as _156 from "./bridge/tx.rpc.msg"; +import * as _157 from "./clob/tx.rpc.msg"; +import * as _158 from "./delaymsg/tx.rpc.msg"; +import * as _159 from "./feetiers/tx.rpc.msg"; +import * as _160 from "./govplus/tx.rpc.msg"; +import * as _161 from "./listing/tx.rpc.msg"; +import * as _162 from "./perpetuals/tx.rpc.msg"; +import * as _163 from "./prices/tx.rpc.msg"; +import * as _164 from "./ratelimit/tx.rpc.msg"; +import * as _165 from "./revshare/tx.rpc.msg"; +import * as _166 from "./rewards/tx.rpc.msg"; +import * as _167 from "./sending/tx.rpc.msg"; +import * as _168 from "./stats/tx.rpc.msg"; +import * as _169 from "./vault/tx.rpc.msg"; +import * as _170 from "./vest/tx.rpc.msg"; +import * as _171 from "./lcd"; +import * as _172 from "./rpc.query"; +import * as _173 from "./rpc.tx"; export namespace dydxprotocol { export const assets = { ..._5, ..._6, ..._7, ..._8, - ..._115, - ..._130 + ..._120, + ..._136 }; export const blocktime = { ..._9, ..._10, ..._11, ..._12, ..._13, - ..._116, - ..._131, - ..._148 + ..._121, + ..._137, + ..._155 }; export const bridge = { ..._14, ..._15, @@ -174,9 +183,9 @@ export namespace dydxprotocol { ..._17, ..._18, ..._19, - ..._117, - ..._132, - ..._149 + ..._122, + ..._138, + ..._156 }; export const clob = { ..._20, ..._21, @@ -192,9 +201,9 @@ export namespace dydxprotocol { ..._31, ..._32, ..._33, - ..._118, - ..._133, - ..._150 + ..._123, + ..._139, + ..._157 }; export namespace daemons { export const bridge = { ..._34 @@ -209,29 +218,29 @@ export namespace dydxprotocol { ..._39, ..._40, ..._41, - ..._119, - ..._134, - ..._151 + ..._124, + ..._140, + ..._158 }; export const epochs = { ..._42, ..._43, ..._44, - ..._120, - ..._135 + ..._125, + ..._141 }; export const feetiers = { ..._45, ..._46, ..._47, ..._48, - ..._121, - ..._136, - ..._152 + ..._126, + ..._142, + ..._159 }; export const govplus = { ..._49, ..._50, ..._51, - ..._137, - ..._153 + ..._143, + ..._160 }; export namespace indexer { export const events = { ..._52 @@ -256,25 +265,26 @@ export namespace dydxprotocol { export const listing = { ..._61, ..._62, ..._63, - ..._138 + ..._144, + ..._161 }; export const perpetuals = { ..._64, ..._65, ..._66, ..._67, ..._68, - ..._122, - ..._139, - ..._154 + ..._127, + ..._145, + ..._162 }; export const prices = { ..._69, ..._70, ..._71, ..._72, ..._73, - ..._123, - ..._140, - ..._155 + ..._128, + ..._146, + ..._163 }; export const ratelimit = { ..._74, ..._75, @@ -282,62 +292,71 @@ export namespace dydxprotocol { ..._77, ..._78, ..._79, - ..._124, - ..._141, - ..._156 + ..._129, + ..._147, + ..._164 }; - export const rewards = { ..._80, + export const revshare = { ..._80, ..._81, ..._82, ..._83, ..._84, - ..._125, - ..._142, - ..._157 + ..._130, + ..._148, + ..._165 }; - export const sending = { ..._85, + export const rewards = { ..._85, ..._86, ..._87, ..._88, - ..._143, - ..._158 + ..._89, + ..._131, + ..._149, + ..._166 }; - export const stats = { ..._89, - ..._90, + export const sending = { ..._90, ..._91, ..._92, ..._93, - ..._126, - ..._144, - ..._159 + ..._150, + ..._167 }; - export const subaccounts = { ..._94, + export const stats = { ..._94, ..._95, ..._96, ..._97, ..._98, - ..._127, - ..._145 + ..._132, + ..._151, + ..._168 }; - export const vault = { ..._99, + export const subaccounts = { ..._99, ..._100, ..._101, ..._102, ..._103, - ..._128, - ..._146, - ..._160 + ..._133, + ..._152 }; - export const vest = { ..._104, + export const vault = { ..._104, ..._105, ..._106, ..._107, - ..._129, - ..._147, - ..._161 + ..._108, + ..._134, + ..._153, + ..._169 }; - export const ClientFactory = { ..._162, - ..._163, - ..._164 + export const vest = { ..._109, + ..._110, + ..._111, + ..._112, + ..._135, + ..._154, + ..._170 + }; + export const ClientFactory = { ..._171, + ..._172, + ..._173 }; } \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/socks/messages.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/socks/messages.ts index 6a0f8fd6f5..d92cbfb43c 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/socks/messages.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/socks/messages.ts @@ -265,6 +265,26 @@ export interface CandleMessageSDKType { version: string; } +export interface BlockHeightMessage { + /** Block height where the contents occur. */ + blockHeight: string; + /** ISO formatted time of the block height. */ + + time: string; + /** Version of the websocket message. */ + + version: string; +} +export interface BlockHeightMessageSDKType { + /** Block height where the contents occur. */ + block_height: string; + /** ISO formatted time of the block height. */ + + time: string; + /** Version of the websocket message. */ + + version: string; +} function createBaseOrderbookMessage(): OrderbookMessage { return { @@ -629,4 +649,69 @@ export const CandleMessage = { return message; } +}; + +function createBaseBlockHeightMessage(): BlockHeightMessage { + return { + blockHeight: "", + time: "", + version: "" + }; +} + +export const BlockHeightMessage = { + encode(message: BlockHeightMessage, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.blockHeight !== "") { + writer.uint32(10).string(message.blockHeight); + } + + if (message.time !== "") { + writer.uint32(18).string(message.time); + } + + if (message.version !== "") { + writer.uint32(26).string(message.version); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): BlockHeightMessage { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseBlockHeightMessage(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.blockHeight = reader.string(); + break; + + case 2: + message.time = reader.string(); + break; + + case 3: + message.version = reader.string(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): BlockHeightMessage { + const message = createBaseBlockHeightMessage(); + message.blockHeight = object.blockHeight ?? ""; + message.time = object.time ?? ""; + message.version = object.version ?? ""; + return message; + } + }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts index 8858e6006c..f61432a744 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts @@ -39,6 +39,9 @@ export const createLCDClient = async ({ ratelimit: new (await import("./ratelimit/query.lcd")).LCDQueryClient({ requestClient }), + revshare: new (await import("./revshare/query.lcd")).LCDQueryClient({ + requestClient + }), rewards: new (await import("./rewards/query.lcd")).LCDQueryClient({ requestClient }), diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts index 9bec4aff2b..6eb10a8756 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts @@ -2,17 +2,35 @@ import * as _m0 from "protobufjs/minimal"; import { DeepPartial } from "../../helpers"; /** GenesisState defines `x/listing`'s genesis state. */ -export interface GenesisState {} +export interface GenesisState { + /** + * hard_cap_for_markets is the hard cap for the number of markets that can be + * listed + */ + hardCapForMarkets: number; +} /** GenesisState defines `x/listing`'s genesis state. */ -export interface GenesisStateSDKType {} +export interface GenesisStateSDKType { + /** + * hard_cap_for_markets is the hard cap for the number of markets that can be + * listed + */ + hard_cap_for_markets: number; +} function createBaseGenesisState(): GenesisState { - return {}; + return { + hardCapForMarkets: 0 + }; } export const GenesisState = { - encode(_: GenesisState, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + encode(message: GenesisState, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.hardCapForMarkets !== 0) { + writer.uint32(8).uint32(message.hardCapForMarkets); + } + return writer; }, @@ -25,6 +43,10 @@ export const GenesisState = { const tag = reader.uint32(); switch (tag >>> 3) { + case 1: + message.hardCapForMarkets = reader.uint32(); + break; + default: reader.skipType(tag & 7); break; @@ -34,8 +56,9 @@ export const GenesisState = { return message; }, - fromPartial(_: DeepPartial): GenesisState { + fromPartial(object: DeepPartial): GenesisState { const message = createBaseGenesisState(); + message.hardCapForMarkets = object.hardCapForMarkets ?? 0; return message; } diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.rpc.Query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.rpc.Query.ts index ab81adee85..cfb432b397 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.rpc.Query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.rpc.Query.ts @@ -1,18 +1,35 @@ import { Rpc } from "../../helpers"; +import * as _m0 from "protobufjs/minimal"; import { QueryClient, createProtobufRpcClient } from "@cosmjs/stargate"; +import { QueryMarketsHardCap, QueryMarketsHardCapResponse } from "./query"; /** Query defines the gRPC querier service. */ -export interface Query {} +export interface Query { + /** Queries for the hard cap number of listed markets */ + marketsHardCap(request?: QueryMarketsHardCap): Promise; +} export class QueryClientImpl implements Query { private readonly rpc: Rpc; constructor(rpc: Rpc) { this.rpc = rpc; + this.marketsHardCap = this.marketsHardCap.bind(this); + } + + marketsHardCap(request: QueryMarketsHardCap = {}): Promise { + const data = QueryMarketsHardCap.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.listing.Query", "MarketsHardCap", data); + return promise.then(data => QueryMarketsHardCapResponse.decode(new _m0.Reader(data))); } } export const createRpcQueryExtension = (base: QueryClient) => { const rpc = createProtobufRpcClient(base); const queryService = new QueryClientImpl(rpc); - return {}; + return { + marketsHardCap(request?: QueryMarketsHardCap): Promise { + return queryService.marketsHardCap(request); + } + + }; }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.ts index 693da49fc4..c9e917823d 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/query.ts @@ -1 +1,99 @@ -export {} \ No newline at end of file +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** Queries for the hard cap on listed markets */ + +export interface QueryMarketsHardCap {} +/** Queries for the hard cap on listed markets */ + +export interface QueryMarketsHardCapSDKType {} +/** Response type indicating the hard cap on listed markets */ + +export interface QueryMarketsHardCapResponse { + /** Response type indicating the hard cap on listed markets */ + hardCap: number; +} +/** Response type indicating the hard cap on listed markets */ + +export interface QueryMarketsHardCapResponseSDKType { + /** Response type indicating the hard cap on listed markets */ + hard_cap: number; +} + +function createBaseQueryMarketsHardCap(): QueryMarketsHardCap { + return {}; +} + +export const QueryMarketsHardCap = { + encode(_: QueryMarketsHardCap, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryMarketsHardCap { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryMarketsHardCap(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): QueryMarketsHardCap { + const message = createBaseQueryMarketsHardCap(); + return message; + } + +}; + +function createBaseQueryMarketsHardCapResponse(): QueryMarketsHardCapResponse { + return { + hardCap: 0 + }; +} + +export const QueryMarketsHardCapResponse = { + encode(message: QueryMarketsHardCapResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.hardCap !== 0) { + writer.uint32(8).uint32(message.hardCap); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryMarketsHardCapResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryMarketsHardCapResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.hardCap = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryMarketsHardCapResponse { + const message = createBaseQueryMarketsHardCapResponse(); + message.hardCap = object.hardCap ?? 0; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts new file mode 100644 index 0000000000..cf837601bb --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts @@ -0,0 +1,24 @@ +import { Rpc } from "../../helpers"; +import * as _m0 from "protobufjs/minimal"; +import { MsgSetMarketsHardCap, MsgSetMarketsHardCapResponse } from "./tx"; +/** Msg defines the Msg service. */ + +export interface Msg { + /** SetMarketsHardCap sets a hard cap on the number of markets listed */ + setMarketsHardCap(request: MsgSetMarketsHardCap): Promise; +} +export class MsgClientImpl implements Msg { + private readonly rpc: Rpc; + + constructor(rpc: Rpc) { + this.rpc = rpc; + this.setMarketsHardCap = this.setMarketsHardCap.bind(this); + } + + setMarketsHardCap(request: MsgSetMarketsHardCap): Promise { + const data = MsgSetMarketsHardCap.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.listing.Msg", "SetMarketsHardCap", data); + return promise.then(data => MsgSetMarketsHardCapResponse.decode(new _m0.Reader(data))); + } + +} \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts index 693da49fc4..c1210fdd39 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts @@ -1 +1,119 @@ -export {} \ No newline at end of file +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** + * MsgSetMarketsHardCap is used to set a hard cap on the number of markets + * listed + */ + +export interface MsgSetMarketsHardCap { + authority: string; + /** Hard cap for the total number of markets listed */ + + hardCapForMarkets: number; +} +/** + * MsgSetMarketsHardCap is used to set a hard cap on the number of markets + * listed + */ + +export interface MsgSetMarketsHardCapSDKType { + authority: string; + /** Hard cap for the total number of markets listed */ + + hard_cap_for_markets: number; +} +/** MsgSetMarketsHardCapResponse defines the MsgSetMarketsHardCap response */ + +export interface MsgSetMarketsHardCapResponse {} +/** MsgSetMarketsHardCapResponse defines the MsgSetMarketsHardCap response */ + +export interface MsgSetMarketsHardCapResponseSDKType {} + +function createBaseMsgSetMarketsHardCap(): MsgSetMarketsHardCap { + return { + authority: "", + hardCapForMarkets: 0 + }; +} + +export const MsgSetMarketsHardCap = { + encode(message: MsgSetMarketsHardCap, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.authority !== "") { + writer.uint32(10).string(message.authority); + } + + if (message.hardCapForMarkets !== 0) { + writer.uint32(16).uint32(message.hardCapForMarkets); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgSetMarketsHardCap { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgSetMarketsHardCap(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.authority = reader.string(); + break; + + case 2: + message.hardCapForMarkets = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): MsgSetMarketsHardCap { + const message = createBaseMsgSetMarketsHardCap(); + message.authority = object.authority ?? ""; + message.hardCapForMarkets = object.hardCapForMarkets ?? 0; + return message; + } + +}; + +function createBaseMsgSetMarketsHardCapResponse(): MsgSetMarketsHardCapResponse { + return {}; +} + +export const MsgSetMarketsHardCapResponse = { + encode(_: MsgSetMarketsHardCapResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgSetMarketsHardCapResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgSetMarketsHardCapResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): MsgSetMarketsHardCapResponse { + const message = createBaseMsgSetMarketsHardCapResponse(); + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/genesis.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/genesis.ts new file mode 100644 index 0000000000..68e61a5119 --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/genesis.ts @@ -0,0 +1,58 @@ +import { MarketMapperRevenueShareParams, MarketMapperRevenueShareParamsSDKType } from "./params"; +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** GenesisState defines `x/revshare`'s genesis state. */ + +export interface GenesisState { + params?: MarketMapperRevenueShareParams; +} +/** GenesisState defines `x/revshare`'s genesis state. */ + +export interface GenesisStateSDKType { + params?: MarketMapperRevenueShareParamsSDKType; +} + +function createBaseGenesisState(): GenesisState { + return { + params: undefined + }; +} + +export const GenesisState = { + encode(message: GenesisState, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.params !== undefined) { + MarketMapperRevenueShareParams.encode(message.params, writer.uint32(10).fork()).ldelim(); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GenesisState { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGenesisState(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.params = MarketMapperRevenueShareParams.decode(reader, reader.uint32()); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): GenesisState { + const message = createBaseGenesisState(); + message.params = object.params !== undefined && object.params !== null ? MarketMapperRevenueShareParams.fromPartial(object.params) : undefined; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/params.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/params.ts new file mode 100644 index 0000000000..7c3e8c96ca --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/params.ts @@ -0,0 +1,105 @@ +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** MarketMappeRevenueShareParams represents params for the above message */ + +export interface MarketMapperRevenueShareParams { + /** The address which will receive the revenue share payouts */ + address: string; + /** + * The fraction of the fees which will go to the above mentioned address. + * In parts-per-million + */ + + revenueSharePpm: number; + /** + * This parameter defines how many days post market initiation will the + * revenue share be applied for. After valid_days from market initiation + * the revenue share goes down to 0 + */ + + validDays: number; +} +/** MarketMappeRevenueShareParams represents params for the above message */ + +export interface MarketMapperRevenueShareParamsSDKType { + /** The address which will receive the revenue share payouts */ + address: string; + /** + * The fraction of the fees which will go to the above mentioned address. + * In parts-per-million + */ + + revenue_share_ppm: number; + /** + * This parameter defines how many days post market initiation will the + * revenue share be applied for. After valid_days from market initiation + * the revenue share goes down to 0 + */ + + valid_days: number; +} + +function createBaseMarketMapperRevenueShareParams(): MarketMapperRevenueShareParams { + return { + address: "", + revenueSharePpm: 0, + validDays: 0 + }; +} + +export const MarketMapperRevenueShareParams = { + encode(message: MarketMapperRevenueShareParams, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.address !== "") { + writer.uint32(10).string(message.address); + } + + if (message.revenueSharePpm !== 0) { + writer.uint32(16).uint32(message.revenueSharePpm); + } + + if (message.validDays !== 0) { + writer.uint32(24).uint32(message.validDays); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MarketMapperRevenueShareParams { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMarketMapperRevenueShareParams(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.address = reader.string(); + break; + + case 2: + message.revenueSharePpm = reader.uint32(); + break; + + case 3: + message.validDays = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): MarketMapperRevenueShareParams { + const message = createBaseMarketMapperRevenueShareParams(); + message.address = object.address ?? ""; + message.revenueSharePpm = object.revenueSharePpm ?? 0; + message.validDays = object.validDays ?? 0; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.lcd.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.lcd.ts new file mode 100644 index 0000000000..d79b8de832 --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.lcd.ts @@ -0,0 +1,31 @@ +import { LCDClient } from "@osmonauts/lcd"; +import { QueryMarketMapperRevenueShareParams, QueryMarketMapperRevenueShareParamsResponseSDKType, QueryMarketMapperRevShareDetails, QueryMarketMapperRevShareDetailsResponseSDKType } from "./query"; +export class LCDQueryClient { + req: LCDClient; + + constructor({ + requestClient + }: { + requestClient: LCDClient; + }) { + this.req = requestClient; + this.marketMapperRevenueShareParams = this.marketMapperRevenueShareParams.bind(this); + this.marketMapperRevShareDetails = this.marketMapperRevShareDetails.bind(this); + } + /* MarketMapperRevenueShareParams queries the revenue share params for the + market mapper */ + + + async marketMapperRevenueShareParams(_params: QueryMarketMapperRevenueShareParams = {}): Promise { + const endpoint = `dydxprotocol/revshare/market_mapper_rev_share_params`; + return await this.req.get(endpoint); + } + /* Queries market mapper revenue share details for a specific market */ + + + async marketMapperRevShareDetails(params: QueryMarketMapperRevShareDetails): Promise { + const endpoint = `dydxprotocol/revshare/market_mapper_rev_share_details/${params.marketId}`; + return await this.req.get(endpoint); + } + +} \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.rpc.Query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.rpc.Query.ts new file mode 100644 index 0000000000..751bf9f86f --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.rpc.Query.ts @@ -0,0 +1,52 @@ +import { Rpc } from "../../helpers"; +import * as _m0 from "protobufjs/minimal"; +import { QueryClient, createProtobufRpcClient } from "@cosmjs/stargate"; +import { QueryMarketMapperRevenueShareParams, QueryMarketMapperRevenueShareParamsResponse, QueryMarketMapperRevShareDetails, QueryMarketMapperRevShareDetailsResponse } from "./query"; +/** Query defines the gRPC querier service. */ + +export interface Query { + /** + * MarketMapperRevenueShareParams queries the revenue share params for the + * market mapper + */ + marketMapperRevenueShareParams(request?: QueryMarketMapperRevenueShareParams): Promise; + /** Queries market mapper revenue share details for a specific market */ + + marketMapperRevShareDetails(request: QueryMarketMapperRevShareDetails): Promise; +} +export class QueryClientImpl implements Query { + private readonly rpc: Rpc; + + constructor(rpc: Rpc) { + this.rpc = rpc; + this.marketMapperRevenueShareParams = this.marketMapperRevenueShareParams.bind(this); + this.marketMapperRevShareDetails = this.marketMapperRevShareDetails.bind(this); + } + + marketMapperRevenueShareParams(request: QueryMarketMapperRevenueShareParams = {}): Promise { + const data = QueryMarketMapperRevenueShareParams.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.revshare.Query", "MarketMapperRevenueShareParams", data); + return promise.then(data => QueryMarketMapperRevenueShareParamsResponse.decode(new _m0.Reader(data))); + } + + marketMapperRevShareDetails(request: QueryMarketMapperRevShareDetails): Promise { + const data = QueryMarketMapperRevShareDetails.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.revshare.Query", "MarketMapperRevShareDetails", data); + return promise.then(data => QueryMarketMapperRevShareDetailsResponse.decode(new _m0.Reader(data))); + } + +} +export const createRpcQueryExtension = (base: QueryClient) => { + const rpc = createProtobufRpcClient(base); + const queryService = new QueryClientImpl(rpc); + return { + marketMapperRevenueShareParams(request?: QueryMarketMapperRevenueShareParams): Promise { + return queryService.marketMapperRevenueShareParams(request); + }, + + marketMapperRevShareDetails(request: QueryMarketMapperRevShareDetails): Promise { + return queryService.marketMapperRevShareDetails(request); + } + + }; +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.ts new file mode 100644 index 0000000000..b7b3ef17df --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/query.ts @@ -0,0 +1,211 @@ +import { MarketMapperRevenueShareParams, MarketMapperRevenueShareParamsSDKType } from "./params"; +import { MarketMapperRevShareDetails, MarketMapperRevShareDetailsSDKType } from "./revshare"; +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** Queries for the default market mapper revenue share params */ + +export interface QueryMarketMapperRevenueShareParams {} +/** Queries for the default market mapper revenue share params */ + +export interface QueryMarketMapperRevenueShareParamsSDKType {} +/** Response type for QueryMarketMapperRevenueShareParams */ + +export interface QueryMarketMapperRevenueShareParamsResponse { + params?: MarketMapperRevenueShareParams; +} +/** Response type for QueryMarketMapperRevenueShareParams */ + +export interface QueryMarketMapperRevenueShareParamsResponseSDKType { + params?: MarketMapperRevenueShareParamsSDKType; +} +/** Queries market mapper revenue share details for a specific market */ + +export interface QueryMarketMapperRevShareDetails { + /** Queries market mapper revenue share details for a specific market */ + marketId: number; +} +/** Queries market mapper revenue share details for a specific market */ + +export interface QueryMarketMapperRevShareDetailsSDKType { + /** Queries market mapper revenue share details for a specific market */ + market_id: number; +} +/** Response type for QueryMarketMapperRevShareDetails */ + +export interface QueryMarketMapperRevShareDetailsResponse { + details?: MarketMapperRevShareDetails; +} +/** Response type for QueryMarketMapperRevShareDetails */ + +export interface QueryMarketMapperRevShareDetailsResponseSDKType { + details?: MarketMapperRevShareDetailsSDKType; +} + +function createBaseQueryMarketMapperRevenueShareParams(): QueryMarketMapperRevenueShareParams { + return {}; +} + +export const QueryMarketMapperRevenueShareParams = { + encode(_: QueryMarketMapperRevenueShareParams, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryMarketMapperRevenueShareParams { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryMarketMapperRevenueShareParams(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): QueryMarketMapperRevenueShareParams { + const message = createBaseQueryMarketMapperRevenueShareParams(); + return message; + } + +}; + +function createBaseQueryMarketMapperRevenueShareParamsResponse(): QueryMarketMapperRevenueShareParamsResponse { + return { + params: undefined + }; +} + +export const QueryMarketMapperRevenueShareParamsResponse = { + encode(message: QueryMarketMapperRevenueShareParamsResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.params !== undefined) { + MarketMapperRevenueShareParams.encode(message.params, writer.uint32(10).fork()).ldelim(); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryMarketMapperRevenueShareParamsResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryMarketMapperRevenueShareParamsResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.params = MarketMapperRevenueShareParams.decode(reader, reader.uint32()); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryMarketMapperRevenueShareParamsResponse { + const message = createBaseQueryMarketMapperRevenueShareParamsResponse(); + message.params = object.params !== undefined && object.params !== null ? MarketMapperRevenueShareParams.fromPartial(object.params) : undefined; + return message; + } + +}; + +function createBaseQueryMarketMapperRevShareDetails(): QueryMarketMapperRevShareDetails { + return { + marketId: 0 + }; +} + +export const QueryMarketMapperRevShareDetails = { + encode(message: QueryMarketMapperRevShareDetails, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.marketId !== 0) { + writer.uint32(8).uint32(message.marketId); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryMarketMapperRevShareDetails { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryMarketMapperRevShareDetails(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.marketId = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryMarketMapperRevShareDetails { + const message = createBaseQueryMarketMapperRevShareDetails(); + message.marketId = object.marketId ?? 0; + return message; + } + +}; + +function createBaseQueryMarketMapperRevShareDetailsResponse(): QueryMarketMapperRevShareDetailsResponse { + return { + details: undefined + }; +} + +export const QueryMarketMapperRevShareDetailsResponse = { + encode(message: QueryMarketMapperRevShareDetailsResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.details !== undefined) { + MarketMapperRevShareDetails.encode(message.details, writer.uint32(10).fork()).ldelim(); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryMarketMapperRevShareDetailsResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryMarketMapperRevShareDetailsResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.details = MarketMapperRevShareDetails.decode(reader, reader.uint32()); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryMarketMapperRevShareDetailsResponse { + const message = createBaseQueryMarketMapperRevShareDetailsResponse(); + message.details = object.details !== undefined && object.details !== null ? MarketMapperRevShareDetails.fromPartial(object.details) : undefined; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/revshare.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/revshare.ts new file mode 100644 index 0000000000..52648b0bbf --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/revshare.ts @@ -0,0 +1,65 @@ +import * as _m0 from "protobufjs/minimal"; +import { Long, DeepPartial } from "../../helpers"; +/** + * MarketMapperRevShareDetails specifies any details associated with the market + * mapper revenue share + */ + +export interface MarketMapperRevShareDetails { + /** Unix timestamp recorded when the market revenue share expires */ + expirationTs: Long; +} +/** + * MarketMapperRevShareDetails specifies any details associated with the market + * mapper revenue share + */ + +export interface MarketMapperRevShareDetailsSDKType { + /** Unix timestamp recorded when the market revenue share expires */ + expiration_ts: Long; +} + +function createBaseMarketMapperRevShareDetails(): MarketMapperRevShareDetails { + return { + expirationTs: Long.UZERO + }; +} + +export const MarketMapperRevShareDetails = { + encode(message: MarketMapperRevShareDetails, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (!message.expirationTs.isZero()) { + writer.uint32(8).uint64(message.expirationTs); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MarketMapperRevShareDetails { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMarketMapperRevShareDetails(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.expirationTs = (reader.uint64() as Long); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): MarketMapperRevShareDetails { + const message = createBaseMarketMapperRevShareDetails(); + message.expirationTs = object.expirationTs !== undefined && object.expirationTs !== null ? Long.fromValue(object.expirationTs) : Long.UZERO; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/tx.rpc.msg.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/tx.rpc.msg.ts new file mode 100644 index 0000000000..48d8a74998 --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/tx.rpc.msg.ts @@ -0,0 +1,27 @@ +import { Rpc } from "../../helpers"; +import * as _m0 from "protobufjs/minimal"; +import { MsgSetMarketMapperRevenueShare, MsgSetMarketMapperRevenueShareResponse } from "./tx"; +/** Msg defines the Msg service. */ + +export interface Msg { + /** + * SetMarketMapperRevenueShare sets the revenue share for a market + * mapper. + */ + setMarketMapperRevenueShare(request: MsgSetMarketMapperRevenueShare): Promise; +} +export class MsgClientImpl implements Msg { + private readonly rpc: Rpc; + + constructor(rpc: Rpc) { + this.rpc = rpc; + this.setMarketMapperRevenueShare = this.setMarketMapperRevenueShare.bind(this); + } + + setMarketMapperRevenueShare(request: MsgSetMarketMapperRevenueShare): Promise { + const data = MsgSetMarketMapperRevenueShare.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.revshare.Msg", "SetMarketMapperRevenueShare", data); + return promise.then(data => MsgSetMarketMapperRevenueShareResponse.decode(new _m0.Reader(data))); + } + +} \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/tx.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/tx.ts new file mode 100644 index 0000000000..4fdec5faf5 --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/revshare/tx.ts @@ -0,0 +1,114 @@ +import { MarketMapperRevenueShareParams, MarketMapperRevenueShareParamsSDKType } from "./params"; +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** Message to set the market mapper revenue share */ + +export interface MsgSetMarketMapperRevenueShare { + authority: string; + /** Parameters for the revenue share */ + + params?: MarketMapperRevenueShareParams; +} +/** Message to set the market mapper revenue share */ + +export interface MsgSetMarketMapperRevenueShareSDKType { + authority: string; + /** Parameters for the revenue share */ + + params?: MarketMapperRevenueShareParamsSDKType; +} +/** Response to a MsgSetMarketMapperRevenueShare */ + +export interface MsgSetMarketMapperRevenueShareResponse {} +/** Response to a MsgSetMarketMapperRevenueShare */ + +export interface MsgSetMarketMapperRevenueShareResponseSDKType {} + +function createBaseMsgSetMarketMapperRevenueShare(): MsgSetMarketMapperRevenueShare { + return { + authority: "", + params: undefined + }; +} + +export const MsgSetMarketMapperRevenueShare = { + encode(message: MsgSetMarketMapperRevenueShare, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.authority !== "") { + writer.uint32(10).string(message.authority); + } + + if (message.params !== undefined) { + MarketMapperRevenueShareParams.encode(message.params, writer.uint32(18).fork()).ldelim(); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgSetMarketMapperRevenueShare { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgSetMarketMapperRevenueShare(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.authority = reader.string(); + break; + + case 2: + message.params = MarketMapperRevenueShareParams.decode(reader, reader.uint32()); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): MsgSetMarketMapperRevenueShare { + const message = createBaseMsgSetMarketMapperRevenueShare(); + message.authority = object.authority ?? ""; + message.params = object.params !== undefined && object.params !== null ? MarketMapperRevenueShareParams.fromPartial(object.params) : undefined; + return message; + } + +}; + +function createBaseMsgSetMarketMapperRevenueShareResponse(): MsgSetMarketMapperRevenueShareResponse { + return {}; +} + +export const MsgSetMarketMapperRevenueShareResponse = { + encode(_: MsgSetMarketMapperRevenueShareResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgSetMarketMapperRevenueShareResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgSetMarketMapperRevenueShareResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): MsgSetMarketMapperRevenueShareResponse { + const message = createBaseMsgSetMarketMapperRevenueShareResponse(); + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.query.ts index 6dcf3e0f46..fd38646d93 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.query.ts @@ -21,6 +21,7 @@ export const createRPCQueryClient = async ({ perpetuals: (await import("./perpetuals/query.rpc.Query")).createRpcQueryExtension(client), prices: (await import("./prices/query.rpc.Query")).createRpcQueryExtension(client), ratelimit: (await import("./ratelimit/query.rpc.Query")).createRpcQueryExtension(client), + revshare: (await import("./revshare/query.rpc.Query")).createRpcQueryExtension(client), rewards: (await import("./rewards/query.rpc.Query")).createRpcQueryExtension(client), sending: (await import("./sending/query.rpc.Query")).createRpcQueryExtension(client), stats: (await import("./stats/query.rpc.Query")).createRpcQueryExtension(client), diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.tx.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.tx.ts index 2c23915bc5..64a0428be0 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.tx.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/rpc.tx.ts @@ -11,9 +11,11 @@ export const createRPCMsgClient = async ({ delaymsg: new (await import("./delaymsg/tx.rpc.msg")).MsgClientImpl(rpc), feetiers: new (await import("./feetiers/tx.rpc.msg")).MsgClientImpl(rpc), govplus: new (await import("./govplus/tx.rpc.msg")).MsgClientImpl(rpc), + listing: new (await import("./listing/tx.rpc.msg")).MsgClientImpl(rpc), perpetuals: new (await import("./perpetuals/tx.rpc.msg")).MsgClientImpl(rpc), prices: new (await import("./prices/tx.rpc.msg")).MsgClientImpl(rpc), ratelimit: new (await import("./ratelimit/tx.rpc.msg")).MsgClientImpl(rpc), + revshare: new (await import("./revshare/tx.rpc.msg")).MsgClientImpl(rpc), rewards: new (await import("./rewards/tx.rpc.msg")).MsgClientImpl(rpc), sending: new (await import("./sending/tx.rpc.msg")).MsgClientImpl(rpc), stats: new (await import("./stats/tx.rpc.msg")).MsgClientImpl(rpc), diff --git a/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts b/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts index 00f0f6ad8e..6a8c101b83 100644 --- a/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts +++ b/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts @@ -1,3 +1,3 @@ -import * as _108 from "./gogo"; -export const gogoproto = { ..._108 +import * as _113 from "./gogo"; +export const gogoproto = { ..._113 }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/google/bundle.ts b/indexer/packages/v4-protos/src/codegen/google/bundle.ts index dcaafe793c..cefd9154a1 100644 --- a/indexer/packages/v4-protos/src/codegen/google/bundle.ts +++ b/indexer/packages/v4-protos/src/codegen/google/bundle.ts @@ -1,16 +1,16 @@ -import * as _109 from "./api/annotations"; -import * as _110 from "./api/http"; -import * as _111 from "./protobuf/descriptor"; -import * as _112 from "./protobuf/duration"; -import * as _113 from "./protobuf/timestamp"; -import * as _114 from "./protobuf/any"; +import * as _114 from "./api/annotations"; +import * as _115 from "./api/http"; +import * as _116 from "./protobuf/descriptor"; +import * as _117 from "./protobuf/duration"; +import * as _118 from "./protobuf/timestamp"; +import * as _119 from "./protobuf/any"; export namespace google { - export const api = { ..._109, - ..._110 + export const api = { ..._114, + ..._115 }; - export const protobuf = { ..._111, - ..._112, - ..._113, - ..._114 + export const protobuf = { ..._116, + ..._117, + ..._118, + ..._119 }; } \ No newline at end of file diff --git a/indexer/services/auxo/src/constants.ts b/indexer/services/auxo/src/constants.ts index 39f9efedb1..d545afaafe 100644 --- a/indexer/services/auxo/src/constants.ts +++ b/indexer/services/auxo/src/constants.ts @@ -10,6 +10,14 @@ export const BAZOOKA_DB_MIGRATION_PAYLOAD: Uint8Array = new TextEncoder().encode }), ); +export const BAZOOKA_DB_MIGRATION_AND_CREATE_KAFKA_PAYLOAD: Uint8Array = new TextEncoder().encode( + JSON.stringify({ + migrate: true, + create_kafka_topics: true, + clear_kafka_topics: true, + }), +); + export const ECS_SERVICE_NAMES: EcsServiceNames[] = [ EcsServiceNames.COMLINK, EcsServiceNames.ENDER, diff --git a/indexer/services/auxo/src/index.ts b/indexer/services/auxo/src/index.ts index 8bd286e778..4167988bf0 100644 --- a/indexer/services/auxo/src/index.ts +++ b/indexer/services/auxo/src/index.ts @@ -30,6 +30,7 @@ import _ from 'lodash'; import config from './config'; import { + BAZOOKA_DB_MIGRATION_AND_CREATE_KAFKA_PAYLOAD, BAZOOKA_DB_MIGRATION_PAYLOAD, BAZOOKA_LAMBDA_FUNCTION_NAME, ECS_SERVICE_NAMES, @@ -40,7 +41,7 @@ import { AuxoEventJson, EcsServiceNames, TaskDefinitionArnMap } from './types'; /** * Upgrades all services and run migrations * 1. Upgrade Bazooka - * 2. Run db migration in Bazooka + * 2. Run db migration in Bazooka, and update kafka topics * 3. Create new ECS Task Definition for ECS Services with new image * 4. Upgrade all ECS Services (comlink, ender, roundtable, socks, vulcan) */ @@ -66,8 +67,9 @@ export async function handler( // 1. Upgrade Bazooka await upgradeBazooka(lambda, ecr, event); - // 2. Run db migration in Bazooka - await runDbMigration(lambda); + // 2. Run db migration in Bazooka, + // boolean flag used to determine if new kafka topics should be created + await runDbAndKafkaMigration(event.addNewKafkaTopics, lambda); // 3. Create new ECS Task Definition for ECS Services with new image const taskDefinitionArnMap: TaskDefinitionArnMap = await createNewEcsTaskDefinitions( @@ -192,16 +194,21 @@ async function getImageDetail( } -async function runDbMigration( +async function runDbAndKafkaMigration( + createNewKafkaTopics: boolean, lambda: ECRClient, ): Promise { + // TODO (Adam): Add flag to logger to indicate if creating new kafka topics logger.info({ at: 'index#runDbMigration', message: 'Running db migration', }); + const payload = createNewKafkaTopics + ? BAZOOKA_DB_MIGRATION_AND_CREATE_KAFKA_PAYLOAD + : BAZOOKA_DB_MIGRATION_PAYLOAD; const response: InvokeCommandOutput = await lambda.send(new InvokeCommand({ FunctionName: BAZOOKA_LAMBDA_FUNCTION_NAME, - Payload: BAZOOKA_DB_MIGRATION_PAYLOAD, + Payload: payload, // RequestResponse means that the lambda is synchronously invoked InvocationType: 'RequestResponse', })); diff --git a/indexer/services/auxo/src/types.ts b/indexer/services/auxo/src/types.ts index 315f38ae0b..7fff098cc0 100644 --- a/indexer/services/auxo/src/types.ts +++ b/indexer/services/auxo/src/types.ts @@ -13,6 +13,7 @@ export interface AuxoEventJson { region: string; // In our naming we often times use the appreviated region name regionAbbrev: string; + addNewKafkaTopics: boolean; } // EcsServiceName to task definition arn mapping diff --git a/indexer/services/bazooka/src/index.ts b/indexer/services/bazooka/src/index.ts index 5d78cc85f8..91d691ecb3 100644 --- a/indexer/services/bazooka/src/index.ts +++ b/indexer/services/bazooka/src/index.ts @@ -18,6 +18,7 @@ const KAFKA_TOPICS: KafkaTopics[] = [ KafkaTopics.TO_WEBSOCKETS_TRADES, KafkaTopics.TO_WEBSOCKETS_MARKETS, KafkaTopics.TO_WEBSOCKETS_CANDLES, + KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, ]; const DEFAULT_NUM_REPLICAS: number = 3; @@ -30,6 +31,7 @@ const KAFKA_TOPICS_TO_PARTITIONS: { [key in KafkaTopics]: number } = { [KafkaTopics.TO_WEBSOCKETS_TRADES]: 1, [KafkaTopics.TO_WEBSOCKETS_MARKETS]: 1, [KafkaTopics.TO_WEBSOCKETS_CANDLES]: 1, + [KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT]: 1, }; export interface BazookaEventJson { @@ -196,7 +198,7 @@ async function createKafkaTopics( _.forEach(KAFKA_TOPICS, (kafkaTopic: KafkaTopics) => { if (_.includes(existingKafkaTopics, kafkaTopic)) { logger.info({ - at: 'index#clearKafkaTopics', + at: 'index#createKafkaTopics', message: `Cannot create kafka topic that does exist: ${kafkaTopic}`, }); return; diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts index fdd334b562..044ad33030 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/compliance-v2-controller.test.ts @@ -250,7 +250,7 @@ describe('ComplianceV2Controller', () => { const body: any = { address: testConstants.defaultAddress, message: 'Test message', - action: ComplianceAction.ONBOARD, + action: ComplianceAction.CONNECT, signedMessage: 'signedmessage123', pubkey: 'asdfasdf', timestamp: 1620000000, @@ -340,10 +340,11 @@ describe('ComplianceV2Controller', () => { expect(response.body.status).toEqual(ComplianceStatus.COMPLIANT); }); - it('should set status to BLOCKED for ONBOARD action from a restricted country with no existing compliance status', async () => { + it('should set status to BLOCKED for CONNECT action from a restricted country with no existing compliance status and no wallet', async () => { (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); + await dbHelpers.clearData(); const response: any = await sendRequest({ type: RequestMethod.POST, @@ -365,7 +366,7 @@ describe('ComplianceV2Controller', () => { expect(response.body.updatedAt).toBeDefined(); }); - it('should set status to FIRST_STRIKE_CLOSE_ONLY for CONNECT action from a restricted country with no existing compliance status', async () => { + it('should set status to FIRST_STRIKE_CLOSE_ONLY for CONNECT action from a restricted country with no existing compliance status and a wallet', async () => { (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); getGeoComplianceReasonSpy.mockReturnValueOnce(ComplianceReason.US_GEO); isRestrictedCountryHeadersSpy.mockReturnValue(true); @@ -446,59 +447,6 @@ describe('ComplianceV2Controller', () => { expect(response.body.updatedAt).toBeDefined(); }); - it('should be a no-op for ONBOARD action with existing COMPLIANT status', async () => { - await ComplianceStatusTable.create({ - address: testConstants.defaultAddress, - status: ComplianceStatus.COMPLIANT, - }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); - isRestrictedCountryHeadersSpy.mockReturnValue(true); - - const response: any = await sendRequest({ - type: RequestMethod.POST, - path: '/v4/compliance/geoblock', - body, - expectedStatus: 200, - }); - - const data: ComplianceStatusFromDatabase[] = await ComplianceStatusTable.findAll({}, [], {}); - expect(data).toHaveLength(1); - expect(data[0]).toEqual(expect.objectContaining({ - address: testConstants.defaultAddress, - status: ComplianceStatus.COMPLIANT, - })); - expect(response.body.status).toEqual(ComplianceStatus.COMPLIANT); - }); - - it('should be a no-op for ONBOARD action with existing FIRST_STRIKE_CLOSE_ONLY status', async () => { - await ComplianceStatusTable.create({ - address: testConstants.defaultAddress, - status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, - reason: ComplianceReason.US_GEO, - }); - (Secp256k1.verifySignature as jest.Mock).mockResolvedValueOnce(true); - isRestrictedCountryHeadersSpy.mockReturnValue(true); - - const response: any = await sendRequest({ - type: RequestMethod.POST, - path: '/v4/compliance/geoblock', - body, - expectedStatus: 200, - }); - - const data: ComplianceStatusFromDatabase[] = await ComplianceStatusTable.findAll({}, [], {}); - expect(data).toHaveLength(1); - expect(data[0]).toEqual(expect.objectContaining({ - address: testConstants.defaultAddress, - status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, - reason: ComplianceReason.US_GEO, - })); - - expect(response.body.status).toEqual(ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY); - expect(response.body.reason).toEqual(ComplianceReason.US_GEO); - expect(response.body.updatedAt).toBeDefined(); - }); - it('should update status to CLOSE_ONLY for CONNECT action from a restricted country with existing FIRST_STRIKE status', async () => { await ComplianceStatusTable.create({ address: testConstants.defaultAddress, diff --git a/indexer/services/comlink/__tests__/lib/compliance-and-geo-check.test.ts b/indexer/services/comlink/__tests__/lib/compliance-and-geo-check.test.ts index d154d32c9a..3dfb5ee95f 100644 --- a/indexer/services/comlink/__tests__/lib/compliance-and-geo-check.test.ts +++ b/indexer/services/comlink/__tests__/lib/compliance-and-geo-check.test.ts @@ -189,6 +189,26 @@ describe('compliance-check', () => { }); }); + it.each([ + ['query', `/v4/check-compliance-query?address=${testConstants.defaultAddress}`], + ['param', `/v4/check-compliance-param/${testConstants.defaultAddress}`], + ])('does not return 403 if address in request is in FIRST_STRIKE_CLOSE_ONLY and from restricted country (%s)', async ( + _name: string, + path: string, + ) => { + isRestrictedCountrySpy.mockReturnValueOnce(true); + await ComplianceStatusTable.create({ + ...testConstants.compliantStatusData, + status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, + }); + await sendRequestToApp({ + type: RequestMethod.GET, + path, + expressApp: complianceCheckApp, + expectedStatus: 200, + }); + }); + it.each([ ['query', `/v4/check-compliance-query?address=${testConstants.defaultAddress}`], ['param', `/v4/check-compliance-param/${testConstants.defaultAddress}`], diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index b8e2e8fe00..0d8179d195 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -3604,6 +3604,7 @@ This operation does not require authentication |*anonymous*|MANUAL| |*anonymous*|US_GEO| |*anonymous*|CA_GEO| +|*anonymous*|GB_GEO| |*anonymous*|SANCTIONED_GEO| |*anonymous*|COMPLIANCE_PROVIDER| diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index c0b6807a52..397f608b77 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -369,6 +369,7 @@ "MANUAL", "US_GEO", "CA_GEO", + "GB_GEO", "SANCTIONED_GEO", "COMPLIANCE_PROVIDER" ], diff --git a/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts b/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts index 3c5f6f49d9..394fb0d168 100644 --- a/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/compliance-v2-controller.ts @@ -9,9 +9,12 @@ import { ComplianceStatus, ComplianceStatusFromDatabase, ComplianceStatusTable, + WalletFromDatabase, + WalletTable, } from '@dydxprotocol-indexer/postgres'; import express from 'express'; import { matchedData } from 'express-validator'; +import _ from 'lodash'; import { DateTime } from 'luxon'; import { Controller, Get, Path, Route, @@ -28,14 +31,15 @@ import { getIpAddr } from '../../../lib/utils'; import { CheckAddressSchema } from '../../../lib/validation/schemas'; import { handleValidationErrors } from '../../../request-helpers/error-handler'; import ExportResponseCodeStats from '../../../request-helpers/export-response-code-stats'; -import { ComplianceRequest, ComplianceV2Response, SetComplianceStatusRequest } from '../../../types'; +import { + ComplianceRequest, ComplianceV2Response, SetComplianceStatusRequest, +} from '../../../types'; import { ComplianceControllerHelper } from './compliance-controller'; const router: express.Router = express.Router(); const controllerName: string = 'compliance-v2-controller'; export enum ComplianceAction { - ONBOARD = 'ONBOARD', CONNECT = 'CONNECT', VALID_SURVEY = 'VALID_SURVEY', INVALID_SURVEY = 'INVALID_SURVEY', @@ -235,98 +239,31 @@ router.post( ); } - /** - * If the address doesn't exist in the compliance table: - * - if the request is from a restricted country: - * - if the action is ONBOARD, set the status to BLOCKED - * - if the action is CONNECT, set the status to FIRST_STRIKE_CLOSE_ONLY - * - else if the request is from a non-restricted country: - * - set the status to COMPLIANT - * - * if the address is COMPLIANT: - * - the ONLY action should be CONNECT. ONBOARD/VALID_SURVEY/INVALID_SURVEY are no-ops. - * - if the request is from a restricted country: - * - set the status to FIRST_STRIKE_CLOSE_ONLY - * - * if the address is FIRST_STRIKE_CLOSE_ONLY: - * - the ONLY actions should be VALID_SURVEY/INVALID_SURVEY/CONNECT. ONBOARD/CONNECT - * are no-ops. - * - if the action is VALID_SURVEY: - * - set the status to FIRST_STRIKE - * - if the action is INVALID_SURVEY: - * - set the status to CLOSE_ONLY - * - * if the address is FIRST_STRIKE: - * - the ONLY action should be CONNECT. ONBOARD/VALID_SURVEY/INVALID_SURVEY are no-ops. - * - if the request is from a restricted country: - * - set the status to CLOSE_ONLY - */ - const complianceStatus: ComplianceStatusFromDatabase[] = await - ComplianceStatusTable.findAll( - { address: [address] }, - [], - ); - let complianceStatusFromDatabase: ComplianceStatusFromDatabase | undefined; + const [ + complianceStatus, + wallet, + ]: [ + ComplianceStatusFromDatabase[], + WalletFromDatabase | undefined, + ] = await Promise.all([ + ComplianceStatusTable.findAll( + { address: [address] }, + [], + ), + WalletTable.findById(address), + ]); + const updatedAt: string = DateTime.utc().toISO(); - if (complianceStatus.length === 0) { - if (isRestrictedCountryHeaders(req.headers as CountryHeaders)) { - if (action === ComplianceAction.ONBOARD) { - complianceStatusFromDatabase = await ComplianceStatusTable.upsert({ - address, - status: ComplianceStatus.BLOCKED, - reason: getGeoComplianceReason(req.headers as CountryHeaders)!, - updatedAt, - }); - } else if (action === ComplianceAction.CONNECT) { - complianceStatusFromDatabase = await ComplianceStatusTable.upsert({ - address, - status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, - reason: getGeoComplianceReason(req.headers as CountryHeaders)!, - updatedAt, - }); - } - } else { - complianceStatusFromDatabase = await ComplianceStatusTable.upsert({ - address, - status: ComplianceStatus.COMPLIANT, - updatedAt, - }); - } - } else { - complianceStatusFromDatabase = complianceStatus[0]; - if ( - complianceStatus[0].status === ComplianceStatus.FIRST_STRIKE || - complianceStatus[0].status === ComplianceStatus.COMPLIANT - ) { - if ( - isRestrictedCountryHeaders(req.headers as CountryHeaders) && - action === ComplianceAction.CONNECT - ) { - complianceStatusFromDatabase = await ComplianceStatusTable.update({ - address, - status: COMPLIANCE_PROGRESSION[complianceStatus[0].status], - reason: getGeoComplianceReason(req.headers as CountryHeaders)!, - updatedAt, - }); - } - } else if ( - complianceStatus[0].status === ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY - ) { - if (action === ComplianceAction.VALID_SURVEY) { - complianceStatusFromDatabase = await ComplianceStatusTable.update({ - address, - status: ComplianceStatus.FIRST_STRIKE, - updatedAt, - }); - } else if (action === ComplianceAction.INVALID_SURVEY) { - complianceStatusFromDatabase = await ComplianceStatusTable.update({ - address, - status: ComplianceStatus.CLOSE_ONLY, - updatedAt, - }); - } - } - } + const complianceStatusFromDatabase: + ComplianceStatusFromDatabase | undefined = await upsertComplianceStatus( + req, + action, + address, + wallet, + complianceStatus, + updatedAt, + ); + const response = { status: complianceStatusFromDatabase!.status, reason: complianceStatusFromDatabase!.reason, @@ -360,6 +297,102 @@ function generateAddress(pubkeyArray: Uint8Array): string { return toBech32('dydx', ripemd160(sha256(pubkeyArray))); } +/** + * If the address doesn't exist in the compliance table: + * - if the request is from a restricted country: + * - if the action is CONNECT and no wallet, set the status to BLOCKED + * - if the action is CONNECT and wallet exists, set the status to FIRST_STRIKE_CLOSE_ONLY + * - else if the request is from a non-restricted country: + * - set the status to COMPLIANT + * + * if the address is COMPLIANT: + * - the ONLY action should be CONNECT. VALID_SURVEY/INVALID_SURVEY are no-ops. + * - if the request is from a restricted country: + * - set the status to FIRST_STRIKE_CLOSE_ONLY + * + * if the address is FIRST_STRIKE_CLOSE_ONLY: + * - the ONLY actions should be VALID_SURVEY/INVALID_SURVEY/CONNECT. CONNECT + * are no-ops. + * - if the action is VALID_SURVEY: + * - set the status to FIRST_STRIKE + * - if the action is INVALID_SURVEY: + * - set the status to CLOSE_ONLY + * + * if the address is FIRST_STRIKE: + * - the ONLY action should be CONNECT. VALID_SURVEY/INVALID_SURVEY are no-ops. + * - if the request is from a restricted country: + * - set the status to CLOSE_ONLY + */ +// eslint-disable-next-line @typescript-eslint/require-await +async function upsertComplianceStatus( + req: express.Request, + action: ComplianceAction, + address: string, + wallet: WalletFromDatabase | undefined, + complianceStatus: ComplianceStatusFromDatabase[], + updatedAt: string, +): Promise { + if (complianceStatus.length === 0) { + if (!isRestrictedCountryHeaders(req.headers as CountryHeaders)) { + return ComplianceStatusTable.upsert({ + address, + status: ComplianceStatus.COMPLIANT, + updatedAt, + }); + } + + // If address is restricted and is not onboarded then block + if (_.isUndefined(wallet)) { + return ComplianceStatusTable.upsert({ + address, + status: ComplianceStatus.BLOCKED, + reason: getGeoComplianceReason(req.headers as CountryHeaders)!, + updatedAt, + }); + } + + return ComplianceStatusTable.upsert({ + address, + status: ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY, + reason: getGeoComplianceReason(req.headers as CountryHeaders)!, + updatedAt, + }); + } + + if ( + complianceStatus[0].status === ComplianceStatus.FIRST_STRIKE || + complianceStatus[0].status === ComplianceStatus.COMPLIANT + ) { + if ( + isRestrictedCountryHeaders(req.headers as CountryHeaders) && + action === ComplianceAction.CONNECT + ) { + return ComplianceStatusTable.update({ + address, + status: COMPLIANCE_PROGRESSION[complianceStatus[0].status], + reason: getGeoComplianceReason(req.headers as CountryHeaders)!, + updatedAt, + }); + } + } else if (complianceStatus[0].status === ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY) { + if (action === ComplianceAction.VALID_SURVEY) { + return ComplianceStatusTable.update({ + address, + status: ComplianceStatus.FIRST_STRIKE, + updatedAt, + }); + } else if (action === ComplianceAction.INVALID_SURVEY) { + return ComplianceStatusTable.update({ + address, + status: ComplianceStatus.CLOSE_ONLY, + updatedAt, + }); + } + } + + return complianceStatus[0]; +} + if (config.EXPOSE_SET_COMPLIANCE_ENDPOINT) { router.post( '/setStatus', diff --git a/indexer/services/comlink/src/helpers/compliance/compliance-utils.ts b/indexer/services/comlink/src/helpers/compliance/compliance-utils.ts index 7aead50b20..09d25949e0 100644 --- a/indexer/services/comlink/src/helpers/compliance/compliance-utils.ts +++ b/indexer/services/comlink/src/helpers/compliance/compliance-utils.ts @@ -10,6 +10,8 @@ export function getGeoComplianceReason( return ComplianceReason.US_GEO; } else if (country === 'CA') { return ComplianceReason.CA_GEO; + } else if (country === 'GB') { + return ComplianceReason.GB_GEO; } else { return ComplianceReason.SANCTIONED_GEO; } diff --git a/indexer/services/comlink/src/lib/compliance-and-geo-check.ts b/indexer/services/comlink/src/lib/compliance-and-geo-check.ts index bee0a96900..983fb1b872 100644 --- a/indexer/services/comlink/src/lib/compliance-and-geo-check.ts +++ b/indexer/services/comlink/src/lib/compliance-and-geo-check.ts @@ -47,7 +47,9 @@ export async function complianceAndGeoCheck( { readReplica: true }, ); if (updatedStatus.length > 0) { - if (updatedStatus[0].status === ComplianceStatus.CLOSE_ONLY) { + if (updatedStatus[0].status === ComplianceStatus.CLOSE_ONLY || + updatedStatus[0].status === ComplianceStatus.FIRST_STRIKE_CLOSE_ONLY + ) { return next(); } else if (updatedStatus[0].status === ComplianceStatus.BLOCKED) { return create4xxResponse( diff --git a/indexer/services/ender/__tests__/lib/block-processor.test.ts b/indexer/services/ender/__tests__/lib/block-processor.test.ts index 6f8c18a6f3..310b435215 100644 --- a/indexer/services/ender/__tests__/lib/block-processor.test.ts +++ b/indexer/services/ender/__tests__/lib/block-processor.test.ts @@ -24,6 +24,7 @@ import { BatchedHandlers } from '../../src/lib/batched-handlers'; import { SyncHandlers } from '../../src/lib/sync-handlers'; import { mock, MockProxy } from 'jest-mock-extended'; import { createPostgresFunctions } from '../../src/helpers/postgres/postgres-functions'; +import { BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION, KafkaTopics } from '@dydxprotocol-indexer/kafka'; describe('block-processor', () => { let batchedHandlers: MockProxy; @@ -162,4 +163,60 @@ describe('block-processor', () => { batchedHandlers.process.mock.invocationCallOrder[0], ); }); + + it('Adds a block height message to the Kafka publisher', async () => { + const block: IndexerTendermintBlock = createIndexerTendermintBlock( + defaultHeight, + defaultTime, + events, + [ + defaultTxHash, + defaultTxHash2, + ], + ); + + const txId: number = await Transaction.start(); + const blockProcessor: BlockProcessor = new BlockProcessor( + block, + txId, + defaultDateTime.toString(), + ); + const processor = await blockProcessor.process(); + await Transaction.commit(txId); + expect(processor.blockHeightMessages).toHaveLength(1); + expect(processor.blockHeightMessages[0].blockHeight).toEqual(String(defaultHeight)); + expect(processor.blockHeightMessages[0].version) + .toEqual(BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION); + expect(processor.blockHeightMessages[0].time).toEqual(defaultDateTime.toString()); + }); + + it('createBlockHeightMsg creates a BlockHeightMessage', async () => { + const block: IndexerTendermintBlock = createIndexerTendermintBlock( + defaultHeight, + defaultTime, + events, + [ + defaultTxHash, + defaultTxHash2, + ], + ); + + const txId: number = await Transaction.start(); + const blockProcessor: BlockProcessor = new BlockProcessor( + block, + txId, + defaultDateTime.toString(), + ); + await Transaction.commit(txId); + + const msg = blockProcessor.createBlockHeightMsg(); + expect(msg).toEqual({ + topic: KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + message: { + blockHeight: String(defaultHeight), + time: defaultDateTime.toString(), + version: BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION, + }, + }); + }); }); diff --git a/indexer/services/ender/__tests__/lib/kafka-publisher.test.ts b/indexer/services/ender/__tests__/lib/kafka-publisher.test.ts index 3656be98b3..a73eaef01c 100644 --- a/indexer/services/ender/__tests__/lib/kafka-publisher.test.ts +++ b/indexer/services/ender/__tests__/lib/kafka-publisher.test.ts @@ -17,13 +17,16 @@ import { TradeType, TransferFromDatabase, } from '@dydxprotocol-indexer/postgres'; -import { IndexerSubaccountId, SubaccountMessage, TradeMessage } from '@dydxprotocol-indexer/v4-protos'; +import { + BlockHeightMessage, IndexerSubaccountId, SubaccountMessage, TradeMessage, +} from '@dydxprotocol-indexer/v4-protos'; import Big from 'big.js'; import _ from 'lodash'; import { AnnotatedSubaccountMessage, ConsolidatedKafkaEvent, SingleTradeMessage } from '../../src/lib/types'; import { KafkaPublisher } from '../../src/lib/kafka-publisher'; import { + defaultDateTime, defaultSubaccountMessage, defaultTradeContent, defaultTradeKafkaEvent, @@ -43,6 +46,7 @@ import { } from '../../src/helpers/kafka-helper'; import { DateTime } from 'luxon'; import { convertToSubaccountMessage } from '../../src/lib/helper'; +import { defaultBlock } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; describe('kafka-publisher', () => { let producerSendMock: jest.SpyInstance; @@ -105,6 +109,30 @@ describe('kafka-publisher', () => { }); }); + it('successfully publishes block height messages', async () => { + const message: BlockHeightMessage = { + blockHeight: String(defaultBlock), + version: '1.0.0', + time: defaultDateTime.toString(), + }; + const blockHeightEvent: ConsolidatedKafkaEvent = { + topic: KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + message, + }; + + const publisher: KafkaPublisher = new KafkaPublisher(); + publisher.addEvents([blockHeightEvent]); + + await publisher.publish(); + expect(producerSendMock).toHaveBeenCalledTimes(1); + expect(producerSendMock).toHaveBeenCalledWith({ + topic: blockHeightEvent.topic, + messages: [{ + value: Buffer.from(BlockHeightMessage.encode(blockHeightEvent.message).finish()), + }], + }); + }); + describe('sortTradeEvents', () => { const trade: SingleTradeMessage = contentToSingleTradeMessage( {} as TradeContent, diff --git a/indexer/services/ender/__tests__/lib/on-message.test.ts b/indexer/services/ender/__tests__/lib/on-message.test.ts index 973830b5f5..ae521b3351 100644 --- a/indexer/services/ender/__tests__/lib/on-message.test.ts +++ b/indexer/services/ender/__tests__/lib/on-message.test.ts @@ -671,7 +671,7 @@ describe('on-message', () => { expectBlock(defaultHeight.toString(), defaultDateTime.toISO()), ]); - expect(producerSendMock).toHaveBeenCalledTimes(2); + expect(producerSendMock).toHaveBeenCalledTimes(3); // First message batch sent should contain the first message expect(producerSendMock.mock.calls[0][0].messages).toHaveLength(1); // Second message batch should contain the second message diff --git a/indexer/services/ender/src/lib/block-processor.ts b/indexer/services/ender/src/lib/block-processor.ts index c54fa8e14b..4e7287110f 100644 --- a/indexer/services/ender/src/lib/block-processor.ts +++ b/indexer/services/ender/src/lib/block-processor.ts @@ -1,9 +1,11 @@ /* eslint-disable max-len */ import { logger, stats, STATS_NO_SAMPLING } from '@dydxprotocol-indexer/base'; +import { BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION, KafkaTopics } from '@dydxprotocol-indexer/kafka'; import { storeHelpers, } from '@dydxprotocol-indexer/postgres'; import { + BlockHeightMessage, IndexerTendermintBlock, IndexerTendermintEvent, } from '@dydxprotocol-indexer/v4-protos'; @@ -33,6 +35,7 @@ import { indexerTendermintEventToEventProtoWithType, indexerTendermintEventToTra import { KafkaPublisher } from './kafka-publisher'; import { SyncHandlers, SYNCHRONOUS_SUBTYPES } from './sync-handlers'; import { + ConsolidatedKafkaEvent, DydxIndexerSubtypes, EventMessage, EventProtoWithTypeAndVersion, GroupedEvents, } from './types'; @@ -225,6 +228,18 @@ export class BlockProcessor { }); } + createBlockHeightMsg(): ConsolidatedKafkaEvent { + const message: BlockHeightMessage = { + blockHeight: String(this.block.height), + version: BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION, + time: this.block.time?.toISOString() ?? '', + }; + return { + topic: KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + message, + }; + } + private async processEvents(): Promise { const kafkaPublisher: KafkaPublisher = new KafkaPublisher(); @@ -271,6 +286,9 @@ export class BlockProcessor { ); } + // Create a block message from the current block + kafkaPublisher.addEvent(this.createBlockHeightMsg()); + // in genesis, handle sync events first, then batched events. // in other blocks, handle batched events first, then sync events. if (this.block.height === 0) { diff --git a/indexer/services/ender/src/lib/kafka-publisher.ts b/indexer/services/ender/src/lib/kafka-publisher.ts index 8be583c0cc..8295ca5539 100644 --- a/indexer/services/ender/src/lib/kafka-publisher.ts +++ b/indexer/services/ender/src/lib/kafka-publisher.ts @@ -8,6 +8,7 @@ import { } from '@dydxprotocol-indexer/kafka'; import { FillSubaccountMessageContents, TradeMessageContents } from '@dydxprotocol-indexer/postgres'; import { + BlockHeightMessage, CandleMessage, MarketMessage, OffChainUpdateV1, @@ -20,7 +21,10 @@ import _ from 'lodash'; import config from '../config'; import { convertToSubaccountMessage } from './helper'; import { - AnnotatedSubaccountMessage, ConsolidatedKafkaEvent, SingleTradeMessage, VulcanMessage, + AnnotatedSubaccountMessage, + ConsolidatedKafkaEvent, + SingleTradeMessage, + VulcanMessage, } from './types'; type TopicKafkaMessages = { @@ -31,9 +35,10 @@ type TopicKafkaMessages = { type OrderedMessage = AnnotatedSubaccountMessage | SingleTradeMessage; type Message = AnnotatedSubaccountMessage | SingleTradeMessage | MarketMessage | -CandleMessage | VulcanMessage; +CandleMessage | VulcanMessage | BlockHeightMessage; export class KafkaPublisher { + blockHeightMessages: BlockHeightMessage[]; subaccountMessages: AnnotatedSubaccountMessage[]; tradeMessages: SingleTradeMessage[]; marketMessages: MarketMessage[]; @@ -41,6 +46,7 @@ export class KafkaPublisher { vulcanMessages: VulcanMessage[]; constructor() { + this.blockHeightMessages = []; this.subaccountMessages = []; this.tradeMessages = []; this.marketMessages = []; @@ -76,6 +82,8 @@ export class KafkaPublisher { return this.candleMessages; case KafkaTopics.TO_VULCAN: return this.vulcanMessages; + case KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT: + return this.blockHeightMessages; default: throw new Error('Invalid Topic'); } @@ -199,6 +207,17 @@ export class KafkaPublisher { private generateAllTopicKafkaMessages(): TopicKafkaMessages[] { const allTopicKafkaMessages: TopicKafkaMessages[] = []; + if (this.blockHeightMessages.length > 0) { + allTopicKafkaMessages.push({ + topic: KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + messages: _.map(this.blockHeightMessages, (message: BlockHeightMessage) => { + return { + value: Buffer.from(Uint8Array.from(BlockHeightMessage.encode(message).finish())), + }; + }), + }); + } + if (this.subaccountMessages.length > 0) { this.aggregateFillEventsForSubaccountMessages(); allTopicKafkaMessages.push({ diff --git a/indexer/services/ender/src/lib/types.ts b/indexer/services/ender/src/lib/types.ts index ce381dbd33..96b2ccea00 100644 --- a/indexer/services/ender/src/lib/types.ts +++ b/indexer/services/ender/src/lib/types.ts @@ -35,6 +35,7 @@ import { DeleveragingEventV1, TradingRewardsEventV1, OpenInterestUpdateEventV1, + BlockHeightMessage, } from '@dydxprotocol-indexer/v4-protos'; import { IHeaders } from 'kafkajs'; import Long from 'long'; @@ -258,6 +259,9 @@ export type ConsolidatedKafkaEvent = { } | { topic: KafkaTopics.TO_VULCAN, message: VulcanMessage, +} | { + topic: KafkaTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + message: BlockHeightMessage }; export enum TransferEventType { diff --git a/indexer/services/roundtable/src/tasks/uncross-orderbook.ts b/indexer/services/roundtable/src/tasks/uncross-orderbook.ts index 355cfe55b8..8ea5024a26 100644 --- a/indexer/services/roundtable/src/tasks/uncross-orderbook.ts +++ b/indexer/services/roundtable/src/tasks/uncross-orderbook.ts @@ -53,7 +53,6 @@ async function uncrossOrderbook( market: PerpetualMarketFromDatabase, orderbookLevels: OrderbookLevels, ): Promise { - console.log(`orderbookLevels: ${JSON.stringify(orderbookLevels)}`); const ticker = market.ticker; // Remove overlapping levels @@ -92,7 +91,7 @@ async function uncrossOrderbook( stats.increment( `${config.SERVICE_NAME}.expected_uncross_orderbook_levels`, removeBidLevels.length, - { side: OrderSide.BUY }, + { side: OrderSide.BUY, clobPairId: market.clobPairId, ticker }, ); for (const bid of removeBidLevels) { const deleted: boolean = await OrderbookLevelsCache.deleteStalePriceLevel({ @@ -119,17 +118,23 @@ async function uncrossOrderbook( ticker, }); } else { - stats.increment(`${config.SERVICE_NAME}.uncross_orderbook`, { side: OrderSide.BUY }); + stats.increment( + `${config.SERVICE_NAME}.uncross_orderbook_succeed`, + { + side: OrderSide.BUY, + clobPairId: market.clobPairId, + ticker, + }, + ); } } stats.increment( `${config.SERVICE_NAME}.expected_uncross_orderbook_levels`, removeAskLevels.length, - { side: OrderSide.SELL }, + { side: OrderSide.SELL, clobPairId: market.clobPairId, ticker }, ); for (const ask of removeAskLevels) { - stats.increment(`${config.SERVICE_NAME}.uncross_orderbook`, { side: OrderSide.SELL }); const deleted: boolean = await OrderbookLevelsCache.deleteStalePriceLevel({ ticker, side: OrderSide.SELL, @@ -154,7 +159,14 @@ async function uncrossOrderbook( ticker, }); } else { - stats.increment(`${config.SERVICE_NAME}.uncross_orderbook`, { side: OrderSide.SELL }); + stats.increment( + `${config.SERVICE_NAME}.uncross_orderbook_succeed`, + { + side: OrderSide.SELL, + clobPairId: market.clobPairId, + ticker, + }, + ); } } } diff --git a/indexer/services/socks/__tests__/constants.ts b/indexer/services/socks/__tests__/constants.ts index 3c33c31ebf..ef8bc17d02 100644 --- a/indexer/services/socks/__tests__/constants.ts +++ b/indexer/services/socks/__tests__/constants.ts @@ -11,6 +11,7 @@ import { MAX_PARENT_SUBACCOUNTS, } from '@dydxprotocol-indexer/postgres'; import { + BlockHeightMessage, CandleMessage, CandleMessage_Resolution, MarketMessage, @@ -121,3 +122,9 @@ export const defaultTransferContents: TransferSubaccountMessageContents = { createdAt: '2023-10-05T14:48:00.000Z', createdAtHeight: '10', }; + +export const defaultBlockHeightMessage: BlockHeightMessage = { + blockHeight: defaultBlockHeight, + time: '2023-10-05T14:48:00.000Z', + version: '1.0.0', +}; diff --git a/indexer/services/socks/__tests__/helpers/from-kafka-helpers.test.ts b/indexer/services/socks/__tests__/helpers/from-kafka-helpers.test.ts index d4ba610d6c..ebd241b86e 100644 --- a/indexer/services/socks/__tests__/helpers/from-kafka-helpers.test.ts +++ b/indexer/services/socks/__tests__/helpers/from-kafka-helpers.test.ts @@ -1,4 +1,4 @@ -import { getChannels, getMessageToForward } from '../../src/helpers/from-kafka-helpers'; +import { getChannels, getMessageToForward, getMessagesToForward } from '../../src/helpers/from-kafka-helpers'; import { InvalidForwardMessageError, InvalidTopicError } from '../../src/lib/errors'; import { Channel, @@ -21,10 +21,12 @@ import { tradesMessage, defaultChildAccNumber, defaultTransferContents, + defaultBlockHeightMessage, } from '../constants'; import { KafkaMessage } from 'kafkajs'; import { createKafkaMessage } from './kafka'; import { + BlockHeightMessage, CandleMessage, MarketMessage, OrderbookMessage, @@ -52,6 +54,7 @@ describe('from-kafka-helpers', () => { [Channel.V4_ACCOUNTS, Channel.V4_PARENT_ACCOUNTS], ], [WebsocketTopics.TO_WEBSOCKETS_TRADES, [Channel.V4_TRADES]], + [WebsocketTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, [Channel.V4_BLOCK_HEIGHT]], ])('gets correct channel for topic %s', (topic: WebsocketTopics, channels: Channel[]) => { expect(getChannels(topic)).toEqual(channels); }); @@ -156,6 +159,24 @@ describe('from-kafka-helpers', () => { expect(messageToForward.subaccountNumber).toEqual(defaultChildAccNumber); }); + it('gets correct MessageToForward for BlockHeight message', () => { + const message: KafkaMessage = createKafkaMessage( + Buffer.from(Uint8Array.from(BlockHeightMessage.encode(defaultBlockHeightMessage).finish())), + ); + const messageToForward: MessageToForward = getMessagesToForward( + WebsocketTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + message, + ).pop()!; + expect(messageToForward.channel).toEqual(Channel.V4_BLOCK_HEIGHT); + expect(messageToForward.version).toEqual(defaultBlockHeightMessage.version); + expect(messageToForward.contents).toEqual( + { + blockHeight: defaultBlockHeightMessage.blockHeight, + time: defaultBlockHeightMessage.time, + }, + ); + }); + it('filters out transfers between child subaccounts for parent subaccount channel', () => { const transferContents: SubaccountMessageContents = { transfers: { diff --git a/indexer/services/socks/__tests__/lib/message-forwarder.test.ts b/indexer/services/socks/__tests__/lib/message-forwarder.test.ts index a7c1ad75b0..ccdbd4c263 100644 --- a/indexer/services/socks/__tests__/lib/message-forwarder.test.ts +++ b/indexer/services/socks/__tests__/lib/message-forwarder.test.ts @@ -13,6 +13,7 @@ import { startConsumer, TRADES_WEBSOCKET_MESSAGE_VERSION, SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION, + BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION, } from '@dydxprotocol-indexer/kafka'; import { MessageForwarder } from '../../src/lib/message-forwarder'; import WebSocket from 'ws'; @@ -27,7 +28,7 @@ import { WebsocketEvents, } from '../../src/types'; import { Admin } from 'kafkajs'; -import { SubaccountMessage, TradeMessage } from '@dydxprotocol-indexer/v4-protos'; +import { BlockHeightMessage, SubaccountMessage, TradeMessage } from '@dydxprotocol-indexer/v4-protos'; import { dbHelpers, testMocks, @@ -44,6 +45,7 @@ import { defaultSubaccountId, ethClobPairId, ethTicker, + defaultBlockHeightMessage, } from '../constants'; import _ from 'lodash'; import { axiosRequest } from '../../src/lib/axios'; @@ -544,6 +546,97 @@ describe('message-forwarder', () => { })); }); }); + + it('forwards block height messages', (done: jest.DoneCallback) => { + const channel: Channel = Channel.V4_BLOCK_HEIGHT; + const id: string = 'v4_block_height'; + + const blockHeightMessage2 = { + ...defaultBlockHeightMessage, + blockHeight: '1', + }; + + const messageForwarder: MessageForwarder = new MessageForwarder(subscriptions, index); + subscriptions.start(messageForwarder.forwardToClient); + messageForwarder.start(); + + const ws = new WebSocket(WS_HOST); + let connectionId: string; + + ws.on(WebsocketEvents.MESSAGE, async (message) => { + const msg: OutgoingMessage = JSON.parse(message.toString()) as OutgoingMessage; + if (msg.message_id === 0) { + connectionId = msg.connection_id; + } + if (msg.message_id === 1) { + // Check that the initial message is correct. + checkInitialMessage( + msg as SubscribedMessage, + connectionId, + channel, + id, + mockAxiosResponse, + ); + + // Send a couple of block height messages + for (const blockHeightMessage of _.concat( + defaultBlockHeightMessage, + blockHeightMessage2, + )) { + await producer.send({ + topic: WebsocketTopics.TO_WEBSOCKETS_BLOCK_HEIGHT, + messages: [{ + value: Buffer.from( + Uint8Array.from(BlockHeightMessage.encode(blockHeightMessage).finish()), + ), + partition: 0, + timestamp: `${Date.now()}`, + }], + }); + } + } + + const forwardedMsg: ChannelDataMessage = JSON.parse( + message.toString(), + ) as ChannelDataMessage; + + if (msg.message_id >= 2) { + expect(forwardedMsg.connection_id).toBe(connectionId); + expect(forwardedMsg.type).toBe(OutgoingMessageType.CHANNEL_DATA); + expect(forwardedMsg.channel).toBe(channel); + expect(forwardedMsg.id).toBe(id); + expect(forwardedMsg.version).toEqual(BLOCK_HEIGHT_WEBSOCKET_MESSAGE_VERSION); + } + + if (msg.message_id === 2) { + expect(forwardedMsg.contents) + .toEqual( + { + blockHeight: defaultBlockHeightMessage.blockHeight, + time: defaultBlockHeightMessage.time, + }); + } + + if (msg.message_id === 3) { + expect(forwardedMsg.contents) + .toEqual( + { + blockHeight: blockHeightMessage2.blockHeight, + time: blockHeightMessage2.time, + }); + done(); + } + }); + + ws.on('open', () => { + ws.send(JSON.stringify({ + type: IncomingMessageType.SUBSCRIBE, + channel, + id, + batched: false, + })); + }); + }); }); function checkInitialMessage( diff --git a/indexer/services/socks/__tests__/lib/subscriptions.test.ts b/indexer/services/socks/__tests__/lib/subscriptions.test.ts index b721c2bc82..6f38ab1fe6 100644 --- a/indexer/services/socks/__tests__/lib/subscriptions.test.ts +++ b/indexer/services/socks/__tests__/lib/subscriptions.test.ts @@ -41,8 +41,10 @@ describe('Subscriptions', () => { [Channel.V4_ORDERBOOK]: btcTicker, [Channel.V4_TRADES]: btcTicker, [Channel.V4_PARENT_ACCOUNTS]: mockSubaccountId, + [Channel.V4_BLOCK_HEIGHT]: defaultId, }; - const invalidIdsMap: Record, string[]> = { + const invalidIdsMap: + Record, string[]> = { [Channel.V4_ACCOUNTS]: [invalidTicker], [Channel.V4_CANDLES]: [ `${invalidTicker}/${CandleResolution.ONE_DAY}`, @@ -66,6 +68,7 @@ describe('Subscriptions', () => { '/v4/addresses/.+/parentSubaccountNumber/.+', '/v4/orders/parentSubaccountNumber?.+parentSubaccountNumber.+OPEN,UNTRIGGERED,BEST_EFFORT_OPENED,BEST_EFFORT_CANCELED', ], + [Channel.V4_BLOCK_HEIGHT]: ['v4/height'], }; const initialMessage: Object = { a: 'b' }; const country: string = 'AR'; @@ -105,6 +108,7 @@ describe('Subscriptions', () => { [Channel.V4_ORDERBOOK, validIds[Channel.V4_ORDERBOOK]], [Channel.V4_TRADES, validIds[Channel.V4_TRADES]], [Channel.V4_PARENT_ACCOUNTS, validIds[Channel.V4_PARENT_ACCOUNTS]], + [Channel.V4_BLOCK_HEIGHT, validIds[Channel.V4_BLOCK_HEIGHT]], ])('handles valid subscription request to channel %s', async ( channel: Channel, id: string, @@ -316,6 +320,7 @@ describe('Subscriptions', () => { [Channel.V4_MARKETS, validIds[Channel.V4_MARKETS]], [Channel.V4_ORDERBOOK, validIds[Channel.V4_ORDERBOOK]], [Channel.V4_TRADES, validIds[Channel.V4_TRADES]], + [Channel.V4_BLOCK_HEIGHT, validIds[Channel.V4_BLOCK_HEIGHT]], ])('handles valid unsubscription request to channel %s', async ( channel: Channel, id: string, diff --git a/indexer/services/socks/__tests__/websocket/index.test.ts b/indexer/services/socks/__tests__/websocket/index.test.ts index 22d510f7e4..83676f57df 100644 --- a/indexer/services/socks/__tests__/websocket/index.test.ts +++ b/indexer/services/socks/__tests__/websocket/index.test.ts @@ -174,7 +174,9 @@ describe('Index', () => { ALL_CHANNELS.map((channel: Channel) => { return [channel]; }), )('handles valid subscription message for channel: %s', (channel: Channel) => { // Test that markets work with a missing id. - const id: string | undefined = channel === Channel.V4_MARKETS ? undefined : subId; + const id: string | undefined = ( + channel === Channel.V4_MARKETS || channel === Channel.V4_BLOCK_HEIGHT + ) ? undefined : subId; const isBatched: boolean = false; const subMessage: IncomingMessage = createIncomingMessage({ type: IncomingMessageType.SUBSCRIBE, @@ -200,7 +202,9 @@ describe('Index', () => { ALL_CHANNELS.map((channel: Channel) => { return [channel]; }), )('handles valid unsubscribe message for channel: %s', (channel: Channel) => { // Test that markets work with a missing id. - const id: string | undefined = channel === Channel.V4_MARKETS ? undefined : subId; + const id: string | undefined = ( + channel === Channel.V4_MARKETS || channel === Channel.V4_BLOCK_HEIGHT + ) ? undefined : subId; const unSubMessage: IncomingMessage = createIncomingMessage({ type: IncomingMessageType.UNSUBSCRIBE, channel, diff --git a/indexer/services/socks/src/helpers/from-kafka-helpers.ts b/indexer/services/socks/src/helpers/from-kafka-helpers.ts index 581879c7c2..4138f18587 100644 --- a/indexer/services/socks/src/helpers/from-kafka-helpers.ts +++ b/indexer/services/socks/src/helpers/from-kafka-helpers.ts @@ -7,6 +7,7 @@ import { } from '@dydxprotocol-indexer/postgres'; import { getParentSubaccountNum } from '@dydxprotocol-indexer/postgres/build/src/lib/parent-subaccount-helpers'; import { + BlockHeightMessage, CandleMessage, CandleMessage_Resolution, MarketMessage, @@ -16,7 +17,7 @@ import { } from '@dydxprotocol-indexer/v4-protos'; import { KafkaMessage } from 'kafkajs'; -import { TOPIC_TO_CHANNEL, V4_MARKETS_ID } from '../lib/constants'; +import { TOPIC_TO_CHANNEL, V4_BLOCK_HEIGHT_ID, V4_MARKETS_ID } from '../lib/constants'; import { InvalidForwardMessageError, InvalidTopicError } from '../lib/errors'; import { Channel, MessageToForward, WebsocketTopics } from '../types'; @@ -163,6 +164,18 @@ export function getMessagesToForward(topic: string, message: KafkaMessage): Mess version: subaccountMessage.version, }]; } + case WebsocketTopics.TO_WEBSOCKETS_BLOCK_HEIGHT: { + const blockHeightMessage: BlockHeightMessage = BlockHeightMessage.decode(messageBinary); + return [{ + channel: Channel.V4_BLOCK_HEIGHT, + id: V4_BLOCK_HEIGHT_ID, + version: blockHeightMessage.version, + contents: { + blockHeight: blockHeightMessage.blockHeight, + time: blockHeightMessage.time, + }, + }]; + } default: throw new InvalidForwardMessageError(`Unknown topic: ${topic}`); } diff --git a/indexer/services/socks/src/lib/constants.ts b/indexer/services/socks/src/lib/constants.ts index acf32e73f8..b675948333 100644 --- a/indexer/services/socks/src/lib/constants.ts +++ b/indexer/services/socks/src/lib/constants.ts @@ -19,6 +19,7 @@ export const ERR_INVALID_WEBSOCKET_FRAME: string = 'Invalid WebSocket frame'; export const WEBSOCKET_NOT_OPEN: string = 'ws not open'; export const V4_MARKETS_ID: string = 'v4_markets'; +export const V4_BLOCK_HEIGHT_ID: string = 'v4_block_height'; export const TOPIC_TO_CHANNEL: Record = { [WebsocketTopics.TO_WEBSOCKETS_CANDLES]: [Channel.V4_CANDLES], @@ -26,6 +27,7 @@ export const TOPIC_TO_CHANNEL: Record = { [WebsocketTopics.TO_WEBSOCKETS_ORDERBOOKS]: [Channel.V4_ORDERBOOK], [WebsocketTopics.TO_WEBSOCKETS_SUBACCOUNTS]: [Channel.V4_ACCOUNTS, Channel.V4_PARENT_ACCOUNTS], [WebsocketTopics.TO_WEBSOCKETS_TRADES]: [Channel.V4_TRADES], + [WebsocketTopics.TO_WEBSOCKETS_BLOCK_HEIGHT]: [Channel.V4_BLOCK_HEIGHT], }; export const MAX_TIMEOUT_INTEGER: number = 2147483647; diff --git a/indexer/services/socks/src/lib/subscription.ts b/indexer/services/socks/src/lib/subscription.ts index 9cae74b842..c684367f59 100644 --- a/indexer/services/socks/src/lib/subscription.ts +++ b/indexer/services/socks/src/lib/subscription.ts @@ -26,7 +26,7 @@ import { SubscriptionInfo, } from '../types'; import { axiosRequest } from './axios'; -import { V4_MARKETS_ID, WS_CLOSE_CODE_POLICY_VIOLATION } from './constants'; +import { V4_BLOCK_HEIGHT_ID, V4_MARKETS_ID, WS_CLOSE_CODE_POLICY_VIOLATION } from './constants'; import { BlockedError, InvalidChannelError } from './errors'; import { RateLimiter } from './rate-limit'; @@ -112,7 +112,7 @@ export class Subscriptions { return; } - const subscriptionId: string = this.normalizeSubscriptionId(id); + const subscriptionId: string = this.normalizeSubscriptionId(channel, id); const duration: number = this.subscribeRateLimiter.rateLimit({ connectionId, key: channel + subscriptionId, @@ -303,7 +303,7 @@ export class Subscriptions { channel: Channel, id?: string, ): void { - const subscriptionId: string = this.normalizeSubscriptionId(id); + const subscriptionId: string = this.normalizeSubscriptionId(channel, id); if (this.subscriptionLists[connectionId]) { this.subscriptionLists[connectionId] = this.subscriptionLists[connectionId].filter( (e: Subscription) => (e.channel !== channel || e.id !== subscriptionId), @@ -388,8 +388,9 @@ export class Subscriptions { * @returns */ private validateSubscription(channel: Channel, id?: string): boolean { - // Only markets channel does not require an id to subscribe to. - if (channel !== Channel.V4_MARKETS && id === undefined) { + // Only markets & block height channels do not require an id to subscribe to. + if ((channel !== Channel.V4_MARKETS && channel !== Channel.V4_BLOCK_HEIGHT) && + id === undefined) { return false; } switch (channel) { @@ -399,6 +400,7 @@ export class Subscriptions { MAX_PARENT_SUBACCOUNTS * CHILD_SUBACCOUNT_MULTIPLIER, ); } + case (Channel.V4_BLOCK_HEIGHT): case (Channel.V4_MARKETS): { return true; } @@ -437,12 +439,16 @@ export class Subscriptions { /** * Normalizes subscription ids. If the id is undefined, returns the default id for the markets - * channel, which is the only channel that does not have specific ids to subscribe to. + * channel or block height channel which are the only channels that don't + * have specific ids to subscribe to. * NOTE: Validation of the id and channel will happen in other functions. * @param id Subscription id to normalize. * @returns Normalized subscription id. */ - private normalizeSubscriptionId(id?: string): string { + private normalizeSubscriptionId(channel: Channel, id?: string): string { + if (channel === Channel.V4_BLOCK_HEIGHT) { + return id ?? V4_BLOCK_HEIGHT_ID; + } return id ?? V4_MARKETS_ID; } @@ -486,6 +492,9 @@ export class Subscriptions { case (Channel.V4_MARKETS): { return `${COMLINK_URL}/v4/perpetualMarkets`; } + case (Channel.V4_BLOCK_HEIGHT): { + return `${COMLINK_URL}/v4/height`; + } case (Channel.V4_ORDERBOOK): { if (id === undefined) { throw new Error('Invalid undefined channel'); diff --git a/indexer/services/socks/src/types.ts b/indexer/services/socks/src/types.ts index 0f66f7784e..9b42042cca 100644 --- a/indexer/services/socks/src/types.ts +++ b/indexer/services/socks/src/types.ts @@ -22,6 +22,7 @@ export enum Channel { V4_MARKETS = 'v4_markets', V4_CANDLES = 'v4_candles', V4_PARENT_ACCOUNTS = 'v4_parent_subaccounts', + V4_BLOCK_HEIGHT = 'v4_block_height', } export const ALL_CHANNELS = Object.values(Channel); @@ -144,6 +145,7 @@ export enum WebsocketTopics { TO_WEBSOCKETS_TRADES = 'to-websockets-trades', TO_WEBSOCKETS_MARKETS = 'to-websockets-markets', TO_WEBSOCKETS_CANDLES = 'to-websockets-candles', + TO_WEBSOCKETS_BLOCK_HEIGHT = 'to-websockets-block-height', } export enum WebsocketEvents { diff --git a/indexer/services/socks/src/websocket/index.ts b/indexer/services/socks/src/websocket/index.ts index 914e898299..ad2d7edf83 100644 --- a/indexer/services/socks/src/websocket/index.ts +++ b/indexer/services/socks/src/websocket/index.ts @@ -461,7 +461,7 @@ export class Index { private validateSubscriptionForChannel( message: SubscribeMessage | UnsubscribeMessage, ): boolean { - if (message.channel === Channel.V4_MARKETS) { + if (message.channel === Channel.V4_MARKETS || message.channel === Channel.V4_BLOCK_HEIGHT) { return true; } return message.id !== undefined && typeof message.id === 'string'; diff --git a/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts index 8ff4be9f85..1812117a43 100644 --- a/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts +++ b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts @@ -1188,7 +1188,7 @@ function expectWebsocketMessagesSent( expectWebsocketSubaccountMessage( producerSendSpy.mock.calls[callIndex][0], - subaccountMessage, + [subaccountMessage], defaultKafkaHeaders, ); callIndex += 1; diff --git a/indexer/services/vulcan/__tests__/handlers/order-remove-handler.test.ts b/indexer/services/vulcan/__tests__/handlers/order-remove-handler.test.ts index 2c0a50eafd..cfa7b2e34c 100644 --- a/indexer/services/vulcan/__tests__/handlers/order-remove-handler.test.ts +++ b/indexer/services/vulcan/__tests__/handlers/order-remove-handler.test.ts @@ -2221,7 +2221,7 @@ describe('OrderRemoveHandler', () => { const subaccountProducerRecord: ProducerRecord = producerSendSpy.mock.calls[0][0]; expectWebsocketSubaccountMessage( subaccountProducerRecord, - expectedSubaccountMessage, + [expectedSubaccountMessage], defaultKafkaHeaders, ); } diff --git a/indexer/services/vulcan/__tests__/handlers/order-replace-handler.test.ts b/indexer/services/vulcan/__tests__/handlers/order-replace-handler.test.ts index 87bac8f6ad..bf237217c5 100644 --- a/indexer/services/vulcan/__tests__/handlers/order-replace-handler.test.ts +++ b/indexer/services/vulcan/__tests__/handlers/order-replace-handler.test.ts @@ -21,6 +21,7 @@ import { dbHelpers, OrderbookMessageContents, OrderFromDatabase, + OrderStatus, OrderTable, PerpetualMarketFromDatabase, perpetualMarketRefresher, @@ -52,12 +53,13 @@ import { RedisOrder, SubaccountId, SubaccountMessage, + OrderRemovalReason, } from '@dydxprotocol-indexer/v4-protos'; import { KafkaMessage } from 'kafkajs'; import { redisClient, redisClient as client } from '../../src/helpers/redis/redis-controller'; import { onMessage } from '../../src/lib/on-message'; import { expectCanceledOrderStatus, handleInitialOrderPlace } from '../helpers/helpers'; -import { expectOffchainUpdateMessage, expectWebsocketOrderbookMessage, expectWebsocketSubaccountMessage } from '../helpers/websocket-helpers'; +import { expectWebsocketOrderbookMessage, expectWebsocketSubaccountMessage } from '../helpers/websocket-helpers'; import { isStatefulOrder } from '@dydxprotocol-indexer/v4-proto-parser'; import { defaultKafkaHeaders } from '../helpers/constants'; import config from '../../src/config'; @@ -68,11 +70,6 @@ jest.mock('@dydxprotocol-indexer/base', () => ({ wrapBackgroundTask: jest.fn(), })); -interface OffchainUpdateRecord { - key: Buffer, - offchainUpdate: OffChainUpdateV1 -} - describe('order-replace-handler', () => { beforeAll(async () => { await BlockTable.create(testConstants.defaultBlock); @@ -96,16 +93,15 @@ describe('order-replace-handler', () => { ...replacementOrderGoodTilBlockTime, subticks: replacementOrderGoodTilBlockTime.subticks.mul(2), }; - - const replacedOrder: RedisOrder = redisPackage.convertToRedisOrder( + const replacementRedisOrder: RedisOrder = redisPackage.convertToRedisOrder( replacementOrder, testConstants.defaultPerpetualMarket, ); - const replacedOrderGoodTilBlockTime: RedisOrder = redisPackage.convertToRedisOrder( + const replacementRedisOrderGoodTilBlockTime: RedisOrder = redisPackage.convertToRedisOrder( replacementOrderGoodTilBlockTime, testConstants.defaultPerpetualMarket, ); - const replacedOrderDifferentPrice: RedisOrder = redisPackage.convertToRedisOrder( + const replacementRedisOrderDifferentPrice: RedisOrder = redisPackage.convertToRedisOrder( replacementOrderDifferentPrice, testConstants.defaultPerpetualMarket, ); @@ -185,6 +181,7 @@ describe('order-replace-handler', () => { jest.spyOn(CanceledOrdersCache, 'removeOrderFromCaches'); jest.spyOn(stats, 'increment'); jest.spyOn(redisPackage, 'placeOrder'); + jest.spyOn(redisPackage, 'removeOrder'); jest.spyOn(logger, 'error'); jest.spyOn(logger, 'info'); }); @@ -207,7 +204,7 @@ describe('order-replace-handler', () => { dbDefaultOrder, redisTestConstants.defaultOrderUuid, redisTestConstants.defaultReplacementOrderUuid, - replacedOrder, + replacementRedisOrder, true, false, ], @@ -219,7 +216,7 @@ describe('order-replace-handler', () => { dbOrderGoodTilBlockTime, redisTestConstants.defaultOrderUuidGoodTilBlockTime, redisTestConstants.defaultReplacementOrderUuidGTBT, - replacedOrderGoodTilBlockTime, + replacementRedisOrderGoodTilBlockTime, false, false, ], @@ -231,7 +228,7 @@ describe('order-replace-handler', () => { dbDefaultOrder, redisTestConstants.defaultOrderUuid, redisTestConstants.defaultReplacementOrderUuid, - replacedOrder, + replacementRedisOrder, true, true, ], @@ -243,7 +240,7 @@ describe('order-replace-handler', () => { dbOrderGoodTilBlockTime, redisTestConstants.defaultOrderUuidGoodTilBlockTime, redisTestConstants.defaultReplacementOrderUuidGTBT, - replacedOrderGoodTilBlockTime, + replacementRedisOrderGoodTilBlockTime, false, true, ], @@ -255,7 +252,7 @@ describe('order-replace-handler', () => { dbOrder: OrderFromDatabase, expectedOldOrderUuid: string, expectedNewOrderUuid: string, - expectedReplacedOrder: RedisOrder, + expectedReplacementOrder: RedisOrder, expectSubaccountMessage: boolean, hasCanceledOrderId: boolean, ) => { @@ -286,7 +283,6 @@ describe('order-replace-handler', () => { expectSubaccountMessage, ); expectStats(); - // clear mocks jest.clearAllMocks(); // Handle the order replacement off-chain update with the replacement order @@ -296,22 +292,29 @@ describe('order-replace-handler', () => { expectedOldOrderUuid, expectedNewOrderUuid, redisTestConstants.defaultSubaccountUuid, - expectedReplacedOrder, + expectedReplacementOrder, ); expect(OrderbookLevelsCache.updatePriceLevel).not.toHaveBeenCalled(); if (hasCanceledOrderId) { expect(CanceledOrdersCache.removeOrderFromCaches).toHaveBeenCalled(); } - await expectCanceledOrderStatus(expectedOldOrderUuid, CanceledOrderStatus.NOT_CANCELED); + await expectCanceledOrderStatus(expectedOldOrderUuid, CanceledOrderStatus.CANCELED); + await expectCanceledOrderStatus(expectedNewOrderUuid, CanceledOrderStatus.NOT_CANCELED); expect(logger.error).not.toHaveBeenCalled(); + const initialRedisOrder = redisPackage.convertToRedisOrder( + initialOrderToPlace, + testConstants.defaultPerpetualMarket, + ); expectWebsocketMessagesSent( producerSendSpy, - expectedReplacedOrder, + expectedReplacementOrder, dbOrder, testConstants.defaultPerpetualMarket, APIOrderStatusEnum.BEST_EFFORT_OPENED, expectSubaccountMessage, + initialRedisOrder, + 0, ); expectStats(true); }); @@ -325,7 +328,7 @@ describe('order-replace-handler', () => { dbDefaultOrder, redisTestConstants.defaultOrderUuid, redisTestConstants.defaultReplacementOrderUuid, - replacedOrder, + replacementRedisOrder, true, true, false, @@ -338,7 +341,7 @@ describe('order-replace-handler', () => { dbOrderGoodTilBlockTime, redisTestConstants.defaultOrderUuidGoodTilBlockTime, redisTestConstants.defaultReplacementOrderUuidGTBT, - replacedOrderGoodTilBlockTime, + replacementRedisOrderGoodTilBlockTime, false, true, false, @@ -351,7 +354,7 @@ describe('order-replace-handler', () => { dbOrderGoodTilBlockTime, redisTestConstants.defaultOrderUuidGoodTilBlockTime, redisTestConstants.defaultReplacementOrderUuidGTBT, - replacedOrderDifferentPrice, + replacementRedisOrderDifferentPrice, false, true, true, @@ -364,7 +367,7 @@ describe('order-replace-handler', () => { dbOrder: OrderFromDatabase, expectedOldOrderUuid: string, expectedNewOrderUuid: string, - expectedReplacedOrder: RedisOrder, + expectedReplacementOrder: RedisOrder, expectSubaccountMessage: boolean, expectOrderBookUpdate: boolean, expectOrderBookMessage: boolean, @@ -405,7 +408,6 @@ describe('order-replace-handler', () => { APIOrderStatusEnum.BEST_EFFORT_OPENED, expectSubaccountMessage, ); - // clear mocks jest.clearAllMocks(); // Update the order to set it to be resting on the book @@ -433,8 +435,9 @@ describe('order-replace-handler', () => { expectedOldOrderUuid, expectedNewOrderUuid, redisTestConstants.defaultSubaccountUuid, - expectedReplacedOrder, + expectedReplacementOrder, ); + expect(OrderbookLevelsCache.updatePriceLevel).toHaveBeenCalled(); const orderbook: OrderbookLevels = await OrderbookLevelsCache.getOrderBookLevels( testConstants.defaultPerpetualMarket.ticker, client, @@ -448,6 +451,10 @@ describe('order-replace-handler', () => { } expect(logger.error).not.toHaveBeenCalled(); + const initialRedisOrder = redisPackage.convertToRedisOrder( + initialOrderToPlace, + testConstants.defaultPerpetualMarket, + ); const orderbookContents: OrderbookMessageContents = { [OrderbookSide.BIDS]: [[ redisTestConstants.defaultPrice, @@ -456,11 +463,13 @@ describe('order-replace-handler', () => { }; expectWebsocketMessagesSent( producerSendSpy, - expectedReplacedOrder, + expectedReplacementOrder, dbOrder, testConstants.defaultPerpetualMarket, APIOrderStatusEnum.BEST_EFFORT_OPENED, expectSubaccountMessage, + initialRedisOrder, + oldOrderTotalFilled, expectOrderBookMessage ? OrderbookMessage.fromPartial({ contents: JSON.stringify(orderbookContents), @@ -480,7 +489,7 @@ describe('order-replace-handler', () => { dbDefaultOrder, redisTestConstants.defaultOrderUuid, redisTestConstants.defaultReplacementOrderUuid, - replacedOrder, + replacementRedisOrder, true, ], [ @@ -491,7 +500,7 @@ describe('order-replace-handler', () => { dbOrderGoodTilBlockTime, redisTestConstants.defaultOrderUuidGoodTilBlockTime, redisTestConstants.defaultReplacementOrderUuidGTBT, - replacedOrderGoodTilBlockTime, + replacementRedisOrderGoodTilBlockTime, false, ], ])('handles order replacement (with %s), resting on book, 0 remaining quantums', @@ -503,7 +512,7 @@ describe('order-replace-handler', () => { dbOrder: OrderFromDatabase, expectedOldOrderUuid: string, expectedNewOrderUuid: string, - expectedReplacedOrder: RedisOrder, + expectedReplacementOrder: RedisOrder, expectSubaccountMessage: boolean, ) => { synchronizeWrapBackgroundTask(wrapBackgroundTask); @@ -526,7 +535,6 @@ describe('order-replace-handler', () => { expectSubaccountMessage, ); expectStats(); - // clear mocks jest.clearAllMocks(); // Update the order to set it to be resting on the book @@ -542,23 +550,45 @@ describe('order-replace-handler', () => { expectedOldOrderUuid, expectedNewOrderUuid, redisTestConstants.defaultSubaccountUuid, - expectedReplacedOrder, + expectedReplacementOrder, ); - // expect(OrderbookLevelsCache.updatePriceLevel).not.toHaveBeenCalled(); + expect(OrderbookLevelsCache.updatePriceLevel).toHaveBeenCalled(); expect(logger.error).not.toHaveBeenCalled(); + const initialRedisOrder = redisPackage.convertToRedisOrder( + initialOrderToPlace, + testConstants.defaultPerpetualMarket, + ); expectWebsocketMessagesSent( producerSendSpy, - expectedReplacedOrder, + expectedReplacementOrder, dbOrder, testConstants.defaultPerpetualMarket, APIOrderStatusEnum.BEST_EFFORT_OPENED, expectSubaccountMessage, + initialRedisOrder, + Number(initialOrderToPlace.quantums), ); expectStats(true); }, ); + it('replaces order successfully where old order does not exist', async () => { + synchronizeWrapBackgroundTask(wrapBackgroundTask); + const producerSendSpy: jest.SpyInstance = jest.spyOn(producer, 'send').mockReturnThis(); + await onMessage(replacementMessage); + + expect(redisPackage.removeOrder).toHaveBeenCalled(); + expect(redisPackage.placeOrder).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith(expect.objectContaining({ + at: 'OrderReplaceHandler#handle', + message: 'Old order not found in cache', + oldOrderId: redisTestConstants.defaultOrderId, + })); + expect(OrderbookLevelsCache.updatePriceLevel).not.toHaveBeenCalled(); + expectWebsocketMessagesNotSent(producerSendSpy); + }); + it.each([ [ 'missing order', @@ -675,6 +705,7 @@ async function checkOrderReplace( placedSubaccountId: string, expectedOrder: RedisOrder, ): Promise { + expect(redisPackage.removeOrder).toHaveBeenCalled(); const oldRedisOrder: RedisOrder | null = await OrdersCache.getOrder(oldOrderId, client); expect(oldRedisOrder).toBeNull(); @@ -684,6 +715,7 @@ async function checkOrderReplace( client, ); + expect(redisPackage.placeOrder).toHaveBeenCalled(); expect(newRedisOrder).toEqual(expectedOrder); expect(orderIdsForSubaccount).toEqual([placedOrderId]); } @@ -708,88 +740,137 @@ function expectStats(orderWasReplaced: boolean = false): void { function expectWebsocketMessagesSent( producerSendSpy: jest.SpyInstance, - redisOrder: RedisOrder, + replacementRedisOrder: RedisOrder, dbOrder: OrderFromDatabase, perpetualMarket: PerpetualMarketFromDatabase, placementStatus: APIOrderStatus, - expectSubaccountMessage: boolean, + expectPlaceSubaccountMessage: boolean, + oldRedisOrder?: RedisOrder, + oldOrderTotalFilled?: number, expectedOrderbookMessage?: OrderbookMessage, - expectedOffchainUpdate?: OffchainUpdateRecord, ): void { jest.runOnlyPendingTimers(); - // expect one subaccount update message being sent + // expect subaccount message for removing order to be sent let numMessages: number = 0; - if (expectSubaccountMessage) { + if (oldRedisOrder !== undefined || expectPlaceSubaccountMessage) { numMessages += 1; } if (expectedOrderbookMessage !== undefined) { numMessages += 1; } - if (expectedOffchainUpdate !== undefined) { - numMessages += 1; - } + expect(producerSendSpy).toHaveBeenCalledTimes(numMessages); + const expectedSubaccountMessages: SubaccountMessage[] = []; let callIndex: number = 0; - - if (expectedOffchainUpdate) { - expectOffchainUpdateMessage( - producerSendSpy.mock.calls[callIndex][0], - expectedOffchainUpdate.key, - expectedOffchainUpdate.offchainUpdate, + if (oldRedisOrder !== undefined) { + const initialOrderTIF: TimeInForce = protocolTranslations.protocolOrderTIFToTIF( + oldRedisOrder.order!.timeInForce, ); - callIndex += 1; + const isStateful: boolean = isStatefulOrder(oldRedisOrder.order!.orderId!.orderFlags); + + const subaccountRemoveOrderContents: SubaccountMessageContents = { + orders: [{ + id: OrderTable.orderIdToUuid(oldRedisOrder.order!.orderId!), + subaccountId: SubaccountTable.subaccountIdToUuid( + oldRedisOrder.order!.orderId!.subaccountId!, + ), + clientId: oldRedisOrder.order!.orderId!.clientId.toString(), + clobPairId: testConstants.defaultOrderGoodTilBlockTime.clobPairId, + side: protocolTranslations.protocolOrderSideToOrderSide(oldRedisOrder.order!.side), + size: oldRedisOrder.size, + totalOptimisticFilled: protocolTranslations.quantumsToHumanFixedString( + oldOrderTotalFilled!.toString(), + perpetualMarket.atomicResolution, + ), + price: oldRedisOrder.price, + type: protocolTranslations.protocolConditionTypeToOrderType( + oldRedisOrder.order!.conditionType, + ), + status: OrderStatus.CANCELED, + timeInForce: apiTranslations.orderTIFToAPITIF(initialOrderTIF), + postOnly: apiTranslations.isOrderTIFPostOnly(initialOrderTIF), + reduceOnly: oldRedisOrder.order!.reduceOnly, + orderFlags: oldRedisOrder.order!.orderId!.orderFlags.toString(), + ...(isStateful && { + goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(oldRedisOrder.order!), + }), + ...(!isStateful && { + goodTilBlock: protocolTranslations.getGoodTilBlock(oldRedisOrder.order!)!.toString(), + }), + ticker: oldRedisOrder.ticker, + removalReason: OrderRemovalReason[OrderRemovalReason.ORDER_REMOVAL_REASON_USER_CANCELED], + createdAtHeight: dbOrder.createdAtHeight, + updatedAt: dbOrder.updatedAt, + updatedAtHeight: dbOrder.updatedAtHeight, + clientMetadata: oldRedisOrder!.order!.clientMetadata.toString(), + triggerPrice: getTriggerPrice(oldRedisOrder.order!, perpetualMarket), + }], + blockHeight: blockHeightRefresher.getLatestBlockHeight(), + }; + + const orderRemoveSubaccountMessage: SubaccountMessage = SubaccountMessage.fromPartial({ + contents: JSON.stringify(subaccountRemoveOrderContents), + subaccountId: redisTestConstants.defaultSubaccountId, + version: SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION, + }); + expectedSubaccountMessages.push(orderRemoveSubaccountMessage); } - if (expectSubaccountMessage) { + if (expectPlaceSubaccountMessage) { const orderTIF: TimeInForce = protocolTranslations.protocolOrderTIFToTIF( - redisOrder.order!.timeInForce, + replacementRedisOrder.order!.timeInForce, ); - const isStateful: boolean = isStatefulOrder(redisOrder.order!.orderId!.orderFlags); + const isStateful: boolean = isStatefulOrder(replacementRedisOrder.order!.orderId!.orderFlags); const contents: SubaccountMessageContents = { orders: [ { id: OrderTable.orderIdToUuid( - redisOrder.order!.orderId!, + replacementRedisOrder.order!.orderId!, ), subaccountId: SubaccountTable.subaccountIdToUuid( - redisOrder.order!.orderId!.subaccountId!, + replacementRedisOrder.order!.orderId!.subaccountId!, ), - clientId: redisOrder.order!.orderId!.clientId.toString(), + clientId: replacementRedisOrder.order!.orderId!.clientId.toString(), clobPairId: perpetualMarket.clobPairId, - side: protocolTranslations.protocolOrderSideToOrderSide(redisOrder.order!.side), - size: redisOrder.size, - price: redisOrder.price, + side: protocolTranslations.protocolOrderSideToOrderSide( + replacementRedisOrder.order!.side, + ), + size: replacementRedisOrder.size, + price: replacementRedisOrder.price, status: placementStatus, type: protocolTranslations.protocolConditionTypeToOrderType( - redisOrder.order!.conditionType, + replacementRedisOrder.order!.conditionType, ), timeInForce: apiTranslations.orderTIFToAPITIF(orderTIF), postOnly: apiTranslations.isOrderTIFPostOnly(orderTIF), - reduceOnly: redisOrder.order!.reduceOnly, - orderFlags: redisOrder.order!.orderId!.orderFlags.toString(), - goodTilBlock: protocolTranslations.getGoodTilBlock(redisOrder.order!) + reduceOnly: replacementRedisOrder.order!.reduceOnly, + orderFlags: replacementRedisOrder.order!.orderId!.orderFlags.toString(), + goodTilBlock: protocolTranslations.getGoodTilBlock(replacementRedisOrder.order!) ?.toString(), - goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(redisOrder.order!), - ticker: redisOrder.ticker, + goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(replacementRedisOrder.order!), + ticker: replacementRedisOrder.ticker, ...(isStateful && { createdAtHeight: dbOrder.createdAtHeight }), ...(isStateful && { updatedAt: dbOrder.updatedAt }), ...(isStateful && { updatedAtHeight: dbOrder.updatedAtHeight }), - clientMetadata: redisOrder.order!.clientMetadata.toString(), - triggerPrice: getTriggerPrice(redisOrder.order!, perpetualMarket), + clientMetadata: replacementRedisOrder.order!.clientMetadata.toString(), + triggerPrice: getTriggerPrice(replacementRedisOrder.order!, perpetualMarket), }, ], blockHeight: blockHeightRefresher.getLatestBlockHeight(), }; - const subaccountMessage: SubaccountMessage = SubaccountMessage.fromPartial({ + const orderPlaceSubaccountMessage: SubaccountMessage = SubaccountMessage.fromPartial({ contents: JSON.stringify(contents), - subaccountId: SubaccountId.fromPartial(redisOrder.order!.orderId!.subaccountId!), + subaccountId: SubaccountId.fromPartial(replacementRedisOrder.order!.orderId!.subaccountId!), version: SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION, }); + expectedSubaccountMessages.push(orderPlaceSubaccountMessage); + } + if (expectedSubaccountMessages.length > 0) { expectWebsocketSubaccountMessage( producerSendSpy.mock.calls[callIndex][0], - subaccountMessage, + expectedSubaccountMessages, defaultKafkaHeaders, ); callIndex += 1; diff --git a/indexer/services/vulcan/__tests__/helpers/websocket-helpers.ts b/indexer/services/vulcan/__tests__/helpers/websocket-helpers.ts index f97ff27ace..38f2dd48f3 100644 --- a/indexer/services/vulcan/__tests__/helpers/websocket-helpers.ts +++ b/indexer/services/vulcan/__tests__/helpers/websocket-helpers.ts @@ -4,19 +4,22 @@ import { IHeaders, ProducerRecord } from 'kafkajs'; export function expectWebsocketSubaccountMessage( subaccountProducerRecord: ProducerRecord, - expectedSubaccountMessage: SubaccountMessage, + expectedSubaccountMessages: Array, expectedHeaders: IHeaders, ): void { expect(subaccountProducerRecord.topic).toEqual(KafkaTopics.TO_WEBSOCKETS_SUBACCOUNTS); - const subaccountMessageValueBinary: Uint8Array = new Uint8Array( - subaccountProducerRecord.messages[0].value as Buffer, - ); - const headers: IHeaders | undefined = subaccountProducerRecord.messages[0].headers; - const subaccountMessage: SubaccountMessage = SubaccountMessage.decode( - subaccountMessageValueBinary, - ); - expect(headers).toEqual(expectedHeaders); - expect(subaccountMessage).toEqual(expectedSubaccountMessage); + for (let i = 0; i < subaccountProducerRecord.messages.length; i++) { + const subaccountProducerMessage = subaccountProducerRecord.messages[i]; + const subaccountMessageValueBinary: Uint8Array = new Uint8Array( + subaccountProducerMessage.value as Buffer, + ); + const headers: IHeaders | undefined = subaccountProducerMessage.headers; + const subaccountMessage: SubaccountMessage = SubaccountMessage.decode( + subaccountMessageValueBinary, + ); + expect(headers).toEqual(expectedHeaders); + expect(subaccountMessage).toEqual(expectedSubaccountMessages[i]); + } } export function expectWebsocketOrderbookMessage( diff --git a/indexer/services/vulcan/src/handlers/order-replace-handler.ts b/indexer/services/vulcan/src/handlers/order-replace-handler.ts index d380372d81..0b380ed5c6 100644 --- a/indexer/services/vulcan/src/handlers/order-replace-handler.ts +++ b/indexer/services/vulcan/src/handlers/order-replace-handler.ts @@ -1,9 +1,22 @@ -import { logger, runFuncWithTimingStat, stats } from '@dydxprotocol-indexer/base'; -import { createSubaccountWebsocketMessage, KafkaTopics } from '@dydxprotocol-indexer/kafka'; import { + ParseMessageError, logger, runFuncWithTimingStat, stats, +} from '@dydxprotocol-indexer/base'; +import { + createSubaccountWebsocketMessage, + getTriggerPrice, + KafkaTopics, + SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION, +} from '@dydxprotocol-indexer/kafka'; +import { + IsoString, OrderFromDatabase, + OrderStatus, OrderTable, PerpetualMarketFromDatabase, + SubaccountMessageContents, + SubaccountTable, + TimeInForce, + apiTranslations, blockHeightRefresher, perpetualMarketRefresher, protocolTranslations, @@ -30,9 +43,11 @@ import { IndexerOrderId, OffChainUpdateV1, OrderPlaceV1_OrderPlacementStatus, + OrderRemovalReason, OrderReplaceV1, OrderUpdateV1, RedisOrder, + SubaccountMessage, } from '@dydxprotocol-indexer/v4-protos'; import Big from 'big.js'; import { IHeaders, Message } from 'kafkajs'; @@ -41,6 +56,7 @@ import config from '../config'; import { redisClient } from '../helpers/redis/redis-controller'; import { sendMessageWrapper } from '../lib/send-message-helper'; import { Handler } from './handler'; +import { getStateRemainingQuantums } from './helpers'; /** * Handler for OrderReplace messages. This is currently only expected for stateful vault orders. @@ -69,7 +85,25 @@ export class OrderReplaceHandler extends Handler { const oldOrderId: IndexerOrderId = orderReplace.oldOrderId!; /* Remove old order */ - const removeOrderResult: RemoveOrderResult = await this.removeOldOrder(oldOrderId); + let removeOrderResult: RemoveOrderResult = { removed: false }; + try { + removeOrderResult = await this.removeOldOrder(oldOrderId, headers); + } catch (error) { + if (error instanceof ParseMessageError) { + return; + } + } + + /* We don't want to fail if old order is not found (new order should still be placed), + so log and track metric */ + if (!removeOrderResult.removed) { + logger.info({ + at: 'OrderReplaceHandler#handle', + message: 'Old order not found in cache', + oldOrderId, + }); + stats.increment(`${config.SERVICE_NAME}.replace_order_handler.old_order_not_found_in_cache`, 1); + } /* Place new order */ const order: IndexerOrder = orderReplace.order!; @@ -88,6 +122,10 @@ export class OrderReplaceHandler extends Handler { } const redisOrder: RedisOrder = convertToRedisOrder(order, perpetualMarket); const placeOrderResult: PlaceOrderResult = await this.placeNewOrder(redisOrder); + await this.removeOrderFromCanceledOrdersCache( + OrderTable.orderIdToUuid(redisOrder.order?.orderId!), + ); + if (placeOrderResult.replaced) { // This is not expected because the replaced orders either have different order IDs or // should have been removed before being placed again @@ -161,6 +199,11 @@ export class OrderReplaceHandler extends Handler { }; sendMessageWrapper(subaccountMessage, KafkaTopics.TO_WEBSOCKETS_SUBACCOUNTS); } + + await this.addOrderToCanceledOrdersCache( + oldOrderId, + Date.now(), + ); } protected validateOrderReplace(orderReplace: OrderReplaceV1): void { @@ -186,7 +229,38 @@ export class OrderReplaceHandler extends Handler { } } - protected async removeOldOrder(oldOrderId: IndexerOrderId): Promise { + protected async removeOldOrder( + oldOrderId: IndexerOrderId, + headers: IHeaders, + ): Promise { + const order: OrderFromDatabase | undefined = await runFuncWithTimingStat( + OrderTable.findById( + OrderTable.orderIdToUuid(oldOrderId), + ), + this.generateTimingStatsOptions('find_order_for_stateful_cancelation'), + ); + if (order === undefined) { + logger.error({ + at: 'OrderReplaceHandler#removeOldOrder', + message: 'Could not find old order ID to remove in DB', + orderId: oldOrderId, + }); + throw new ParseMessageError(`Could not find old order ID to remove in DB: ${oldOrderId}`); + } + + const perpetualMarket: PerpetualMarketFromDatabase | undefined = perpetualMarketRefresher + .getPerpetualMarketFromClobPairId( + order.clobPairId, + ); + if (perpetualMarket === undefined) { + logger.error({ + at: 'OrderReplaceHandler#removeOldOrder', + message: `Unable to find the perpetual market with clobPairId: ${order.clobPairId}`, + order, + }); + throw new ParseMessageError(`Unable to find the perpetual market with clobPairId: ${order.clobPairId}`); + } + const removeOrderResult: RemoveOrderResult = await runFuncWithTimingStat( removeOrder({ removedOrderId: oldOrderId, @@ -200,9 +274,114 @@ export class OrderReplaceHandler extends Handler { oldOrderId, removeOrderResult, }); + + const stateRemainingQuantums: Big = await getStateRemainingQuantums( + removeOrderResult.removedOrder!, + ); + + // If the remaining amount of the order in state is <= 0, the order is filled and + // does not need to have it's status updated + let canceledOrder: OrderFromDatabase | undefined; + if (stateRemainingQuantums.gt(0)) { + canceledOrder = await runFuncWithTimingStat( + this.cancelOrderInPostgres(oldOrderId), + this.generateTimingStatsOptions('cancel_order_in_postgres'), + ); + } else { + canceledOrder = await runFuncWithTimingStat( + OrderTable.findById(OrderTable.orderIdToUuid(oldOrderId)), + this.generateTimingStatsOptions('find_order'), + ); + } + + const subaccountMessage: Message = { + value: this.createSubaccountWebsocketMessageFromRemoveOrderResult( + removeOrderResult, + canceledOrder, + oldOrderId, + perpetualMarket, + blockHeightRefresher.getLatestBlockHeight(), + ), + headers, + }; + + sendMessageWrapper(subaccountMessage, KafkaTopics.TO_WEBSOCKETS_SUBACCOUNTS); + return removeOrderResult; } + // eslint-disable-next-line @typescript-eslint/require-await + protected async cancelOrderInPostgres( + oldOrderId: IndexerOrderId, + ): Promise { + return OrderTable.update({ + id: OrderTable.orderIdToUuid(oldOrderId), + status: OrderStatus.CANCELED, + }); + } + + protected createSubaccountWebsocketMessageFromRemoveOrderResult( + removeOrderResult: RemoveOrderResult, + canceledOrder: OrderFromDatabase | undefined, + oldOrderId: IndexerOrderId, + perpetualMarket: PerpetualMarketFromDatabase, + blockHeight: string | undefined, + ): Buffer { + const redisOrder: RedisOrder = removeOrderResult.removedOrder!; + const orderTIF: TimeInForce = protocolTranslations.protocolOrderTIFToTIF( + redisOrder.order!.timeInForce, + ); + const createdAtHeight: string | undefined = canceledOrder?.createdAtHeight; + const updatedAt: IsoString | undefined = canceledOrder?.updatedAt; + const updatedAtHeight: string | undefined = canceledOrder?.updatedAtHeight; + const contents: SubaccountMessageContents = { + orders: [ + { + id: OrderTable.orderIdToUuid(redisOrder.order!.orderId!), + subaccountId: SubaccountTable.subaccountIdToUuid( + oldOrderId.subaccountId!, + ), + clientId: oldOrderId.clientId.toString(), + clobPairId: perpetualMarket.clobPairId, + side: protocolTranslations.protocolOrderSideToOrderSide(redisOrder.order!.side), + size: redisOrder.size, + totalOptimisticFilled: protocolTranslations.quantumsToHumanFixedString( + removeOrderResult.totalFilledQuantums!.toString(), + perpetualMarket.atomicResolution, + ), + price: redisOrder.price, + type: protocolTranslations.protocolConditionTypeToOrderType( + redisOrder.order!.conditionType, + ), + status: OrderStatus.CANCELED, + timeInForce: apiTranslations.orderTIFToAPITIF(orderTIF), + postOnly: apiTranslations.isOrderTIFPostOnly(orderTIF), + reduceOnly: redisOrder.order!.reduceOnly, + orderFlags: redisOrder.order!.orderId!.orderFlags.toString(), + goodTilBlock: protocolTranslations.getGoodTilBlock(redisOrder.order!) + ?.toString(), + goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(redisOrder.order!), + ticker: redisOrder.ticker, + removalReason: OrderRemovalReason[OrderRemovalReason.ORDER_REMOVAL_REASON_USER_CANCELED], + ...(createdAtHeight && { createdAtHeight }), + ...(updatedAt && { updatedAt }), + ...(updatedAtHeight && { updatedAtHeight }), + clientMetadata: redisOrder.order!.clientMetadata.toString(), + triggerPrice: getTriggerPrice(redisOrder.order!, perpetualMarket), + }, + ], + ...(blockHeight && { blockHeight }), + }; + + const subaccountMessage: SubaccountMessage = SubaccountMessage.fromPartial({ + contents: JSON.stringify(contents), + subaccountId: oldOrderId.subaccountId!, + version: SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION, + }); + + return Buffer.from(Uint8Array.from(SubaccountMessage.encode(subaccountMessage).finish())); + } + protected async placeNewOrder(redisOrder: RedisOrder): Promise { const placeOrderResult: PlaceOrderResult = await runFuncWithTimingStat( placeOrder({ @@ -390,4 +569,22 @@ export class OrderReplaceHandler extends Handler { return sizeDelta.toFixed(); } + /** + * Adds the removed order to the cancelled orders cache in Redis. + * + * @param orderId + * @param timestampMs + * @protected + */ + protected async addOrderToCanceledOrdersCache( + oldOrderId: IndexerOrderId, + timestampMs: number, + ): Promise { + const orderId: string = OrderTable.orderIdToUuid(oldOrderId); + + await runFuncWithTimingStat( + CanceledOrdersCache.addCanceledOrderId(orderId, timestampMs, redisClient), + this.generateTimingStatsOptions('add_order_to_canceled_order_cache'), + ); + } } diff --git a/proto/dydxprotocol/indexer/socks/messages.proto b/proto/dydxprotocol/indexer/socks/messages.proto index 36c99cfe39..7bc67844c8 100644 --- a/proto/dydxprotocol/indexer/socks/messages.proto +++ b/proto/dydxprotocol/indexer/socks/messages.proto @@ -92,3 +92,14 @@ message CandleMessage { // Version of the websocket message. string version = 4; } + +message BlockHeightMessage { + // Block height where the contents occur. + string block_height = 1; + + // ISO formatted time of the block height. + string time = 2; + + // Version of the websocket message. + string version = 3; +} diff --git a/proto/dydxprotocol/listing/genesis.proto b/proto/dydxprotocol/listing/genesis.proto index 29086f3200..98d3d29a07 100644 --- a/proto/dydxprotocol/listing/genesis.proto +++ b/proto/dydxprotocol/listing/genesis.proto @@ -4,4 +4,8 @@ package dydxprotocol.listing; option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/listing/types"; // GenesisState defines `x/listing`'s genesis state. -message GenesisState {} \ No newline at end of file +message GenesisState { + // hard_cap_for_markets is the hard cap for the number of markets that can be + // listed + uint32 hard_cap_for_markets = 1; +} diff --git a/proto/dydxprotocol/listing/query.proto b/proto/dydxprotocol/listing/query.proto index 4ca01e3b56..1b4554f9c4 100644 --- a/proto/dydxprotocol/listing/query.proto +++ b/proto/dydxprotocol/listing/query.proto @@ -4,4 +4,13 @@ package dydxprotocol.listing; option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/listing/types"; // Query defines the gRPC querier service. -service Query {} \ No newline at end of file +service Query { + // Queries for the hard cap number of listed markets + rpc MarketsHardCap(QueryMarketsHardCap) returns (QueryMarketsHardCapResponse); +} + +// Queries for the hard cap on listed markets +message QueryMarketsHardCap {} + +// Response type indicating the hard cap on listed markets +message QueryMarketsHardCapResponse { uint32 hard_cap = 1; } diff --git a/proto/dydxprotocol/listing/tx.proto b/proto/dydxprotocol/listing/tx.proto index 88e318dfb4..7a853ab619 100644 --- a/proto/dydxprotocol/listing/tx.proto +++ b/proto/dydxprotocol/listing/tx.proto @@ -1,7 +1,29 @@ syntax = "proto3"; package dydxprotocol.listing; +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; + option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/listing/types"; // Msg defines the Msg service. -service Msg {} \ No newline at end of file +service Msg { + // SetMarketsHardCap sets a hard cap on the number of markets listed + rpc SetMarketsHardCap(MsgSetMarketsHardCap) + returns (MsgSetMarketsHardCapResponse); +} + +// MsgSetMarketsHardCap is used to set a hard cap on the number of markets +// listed +message MsgSetMarketsHardCap { + // The address that controls the module (the gov module account). + option (cosmos.msg.v1.signer) = "authority"; + + string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; + + // Hard cap for the total number of markets listed + uint32 hard_cap_for_markets = 2; +} + +// MsgSetMarketsHardCapResponse defines the MsgSetMarketsHardCap response +message MsgSetMarketsHardCapResponse {} diff --git a/proto/dydxprotocol/revshare/genesis.proto b/proto/dydxprotocol/revshare/genesis.proto new file mode 100644 index 0000000000..981a514080 --- /dev/null +++ b/proto/dydxprotocol/revshare/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package dydxprotocol.revshare; + +import "gogoproto/gogo.proto"; +import "dydxprotocol/revshare/params.proto"; + +option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types"; + +// GenesisState defines `x/revshare`'s genesis state. +message GenesisState { + MarketMapperRevenueShareParams params = 1 [ (gogoproto.nullable) = false ]; +} \ No newline at end of file diff --git a/proto/dydxprotocol/revshare/params.proto b/proto/dydxprotocol/revshare/params.proto new file mode 100644 index 0000000000..22cd2a0706 --- /dev/null +++ b/proto/dydxprotocol/revshare/params.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package dydxprotocol.revshare; + +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types"; + +// MarketMappeRevenueShareParams represents params for the above message +message MarketMapperRevenueShareParams { + // The address which will receive the revenue share payouts + string address = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; + + // The fraction of the fees which will go to the above mentioned address. + // In parts-per-million + uint32 revenue_share_ppm = 2; + + // This parameter defines how many days post market initiation will the + // revenue share be applied for. After valid_days from market initiation + // the revenue share goes down to 0 + uint32 valid_days = 3; +} diff --git a/proto/dydxprotocol/revshare/query.proto b/proto/dydxprotocol/revshare/query.proto new file mode 100644 index 0000000000..51ecd4818e --- /dev/null +++ b/proto/dydxprotocol/revshare/query.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; +package dydxprotocol.revshare; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; + +import "dydxprotocol/revshare/params.proto"; +import "dydxprotocol/revshare/revshare.proto"; + +option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types"; + +// Query defines the gRPC querier service. +service Query { + // MarketMapperRevenueShareParams queries the revenue share params for the + // market mapper + rpc MarketMapperRevenueShareParams(QueryMarketMapperRevenueShareParams) + returns (QueryMarketMapperRevenueShareParamsResponse) { + option (google.api.http).get = + "/dydxprotocol/revshare/market_mapper_rev_share_params"; + } + + // Queries market mapper revenue share details for a specific market + rpc MarketMapperRevShareDetails(QueryMarketMapperRevShareDetails) + returns (QueryMarketMapperRevShareDetailsResponse) { + option (google.api.http).get = + "/dydxprotocol/revshare/market_mapper_rev_share_details/{market_id}"; + } +} + +// Queries for the default market mapper revenue share params +message QueryMarketMapperRevenueShareParams {} + +// Response type for QueryMarketMapperRevenueShareParams +message QueryMarketMapperRevenueShareParamsResponse { + MarketMapperRevenueShareParams params = 1 [ (gogoproto.nullable) = false ]; +} + +// Queries market mapper revenue share details for a specific market +message QueryMarketMapperRevShareDetails { uint32 market_id = 1; } + +// Response type for QueryMarketMapperRevShareDetails +message QueryMarketMapperRevShareDetailsResponse { + MarketMapperRevShareDetails details = 1 [ (gogoproto.nullable) = false ]; +} \ No newline at end of file diff --git a/proto/dydxprotocol/revshare/revshare.proto b/proto/dydxprotocol/revshare/revshare.proto new file mode 100644 index 0000000000..210b72d4dc --- /dev/null +++ b/proto/dydxprotocol/revshare/revshare.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package dydxprotocol.revshare; + +option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types"; + +// MarketMapperRevShareDetails specifies any details associated with the market +// mapper revenue share +message MarketMapperRevShareDetails { + // Unix timestamp recorded when the market revenue share expires + uint64 expiration_ts = 1; +} \ No newline at end of file diff --git a/proto/dydxprotocol/revshare/tx.proto b/proto/dydxprotocol/revshare/tx.proto new file mode 100644 index 0000000000..c240efb5d6 --- /dev/null +++ b/proto/dydxprotocol/revshare/tx.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package dydxprotocol.revshare; + +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; +import "gogoproto/gogo.proto"; +import "dydxprotocol/revshare/params.proto"; + +option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types"; + +// Msg defines the Msg service. +service Msg { + // SetMarketMapperRevenueShare sets the revenue share for a market + // mapper. + rpc SetMarketMapperRevenueShare(MsgSetMarketMapperRevenueShare) + returns (MsgSetMarketMapperRevenueShareResponse); +} + +// Message to set the market mapper revenue share +message MsgSetMarketMapperRevenueShare { + // The address that controls the module (the gov module account). + option (cosmos.msg.v1.signer) = "authority"; + + string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; + + // Parameters for the revenue share + MarketMapperRevenueShareParams params = 2 [ (gogoproto.nullable) = false ]; +} + +// Response to a MsgSetMarketMapperRevenueShare +message MsgSetMarketMapperRevenueShareResponse {} diff --git a/protocol/app/app.go b/protocol/app/app.go index 656713122d..03a2a31603 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -169,6 +169,9 @@ import ( ratelimitmodule "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit" ratelimitmodulekeeper "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit/keeper" ratelimitmoduletypes "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit/types" + revsharemodule "github.com/dydxprotocol/v4-chain/protocol/x/revshare" + revsharemodulekeeper "github.com/dydxprotocol/v4-chain/protocol/x/revshare/keeper" + revsharemoduletypes "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" rewardsmodule "github.com/dydxprotocol/v4-chain/protocol/x/rewards" rewardsmodulekeeper "github.com/dydxprotocol/v4-chain/protocol/x/rewards/keeper" rewardsmoduletypes "github.com/dydxprotocol/v4-chain/protocol/x/rewards/types" @@ -310,6 +313,8 @@ type App struct { RewardsKeeper rewardsmodulekeeper.Keeper + RevShareKeeper revsharemodulekeeper.Keeper + StatsKeeper statsmodulekeeper.Keeper SubaccountsKeeper subaccountsmodulekeeper.Keeper @@ -431,6 +436,7 @@ func New( epochsmoduletypes.StoreKey, govplusmoduletypes.StoreKey, vaultmoduletypes.StoreKey, + revsharemoduletypes.StoreKey, ) keys[authtypes.StoreKey] = keys[authtypes.StoreKey].WithLocking() tkeys := storetypes.NewTransientStoreKeys( @@ -1097,6 +1103,7 @@ func New( app.PricesKeeper, app.SendingKeeper, app.SubaccountsKeeper, + app.IndexerEventManager, []string{ lib.GovModuleAddress.String(), delaymsgmoduletypes.ModuleAddress.String(), @@ -1113,6 +1120,15 @@ func New( ) listingModule := listingmodule.NewAppModule(appCodec, app.ListingKeeper) + app.RevShareKeeper = *revsharemodulekeeper.NewKeeper( + appCodec, + keys[revsharemoduletypes.StoreKey], + []string{ + lib.GovModuleAddress.String(), + }, + ) + revShareModule := revsharemodule.NewAppModule(appCodec, app.RevShareKeeper) + /**** Module Options ****/ // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment @@ -1182,6 +1198,7 @@ func New( rateLimitModule, vaultModule, listingModule, + revShareModule, ) app.ModuleManager.SetOrderPreBlockers( @@ -1230,6 +1247,7 @@ func New( delaymsgmoduletypes.ModuleName, vaultmoduletypes.ModuleName, listingmoduletypes.ModuleName, + revsharemoduletypes.ModuleName, ) app.ModuleManager.SetOrderPrepareCheckStaters( @@ -1271,6 +1289,7 @@ func New( delaymsgmoduletypes.ModuleName, vaultmoduletypes.ModuleName, listingmoduletypes.ModuleName, + revsharemoduletypes.ModuleName, authz.ModuleName, // No-op. blocktimemoduletypes.ModuleName, // Must be last ) @@ -1316,6 +1335,7 @@ func New( delaymsgmoduletypes.ModuleName, vaultmoduletypes.ModuleName, listingmoduletypes.ModuleName, + revsharemoduletypes.ModuleName, authz.ModuleName, ) @@ -1357,6 +1377,7 @@ func New( delaymsgmoduletypes.ModuleName, vaultmoduletypes.ModuleName, listingmoduletypes.ModuleName, + revsharemoduletypes.ModuleName, authz.ModuleName, // Auth must be migrated after staking. @@ -1692,8 +1713,6 @@ func (app *App) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { if err != nil { return response, err } - block := app.IndexerEventManager.ProduceBlock(ctx) - app.IndexerEventManager.SendOnchainData(block) return response, err } @@ -1702,6 +1721,8 @@ func (app *App) Precommitter(ctx sdk.Context) { if err := app.ModuleManager.Precommit(ctx); err != nil { panic(err) } + block := app.IndexerEventManager.ProduceBlock(ctx) + app.IndexerEventManager.SendOnchainData(block) } // PrepareCheckStater application updates after commit and before any check state is invoked. diff --git a/protocol/app/app_test.go b/protocol/app/app_test.go index d8ee57a368..eec6a54ced 100644 --- a/protocol/app/app_test.go +++ b/protocol/app/app_test.go @@ -44,6 +44,7 @@ import ( perpetualsmodule "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals" pricesmodule "github.com/dydxprotocol/v4-chain/protocol/x/prices" ratelimitmodule "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit" + revsharemodule "github.com/dydxprotocol/v4-chain/protocol/x/revshare" rewardsmodule "github.com/dydxprotocol/v4-chain/protocol/x/rewards" sendingmodule "github.com/dydxprotocol/v4-chain/protocol/x/sending" statsmodule "github.com/dydxprotocol/v4-chain/protocol/x/stats" @@ -217,6 +218,7 @@ func TestModuleBasics(t *testing.T) { ratelimitmodule.AppModuleBasic{}, vaultmodule.AppModuleBasic{}, listingmodule.AppModuleBasic{}, + revsharemodule.AppModuleBasic{}, ) app := testapp.DefaultTestApp(nil) diff --git a/protocol/app/basic_manager/basic_manager.go b/protocol/app/basic_manager/basic_manager.go index f1cec344bc..cb57669f64 100644 --- a/protocol/app/basic_manager/basic_manager.go +++ b/protocol/app/basic_manager/basic_manager.go @@ -20,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/ibc-go/modules/capability" delaymsgmodule "github.com/dydxprotocol/v4-chain/protocol/x/delaymsg" + listingmodule "github.com/dydxprotocol/v4-chain/protocol/x/listing" custommodule "github.com/dydxprotocol/v4-chain/protocol/app/module" assetsmodule "github.com/dydxprotocol/v4-chain/protocol/x/assets" @@ -32,6 +33,7 @@ import ( perpetualsmodule "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals" pricesmodule "github.com/dydxprotocol/v4-chain/protocol/x/prices" ratelimitmodule "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit" + revsharemodule "github.com/dydxprotocol/v4-chain/protocol/x/revshare" rewardsmodule "github.com/dydxprotocol/v4-chain/protocol/x/rewards" sendingmodule "github.com/dydxprotocol/v4-chain/protocol/x/sending" statsmodule "github.com/dydxprotocol/v4-chain/protocol/x/stats" @@ -93,5 +95,7 @@ var ( ratelimitmodule.AppModuleBasic{}, govplusmodule.AppModuleBasic{}, vaultmodule.AppModuleBasic{}, + revsharemodule.AppModuleBasic{}, + listingmodule.AppModuleBasic{}, ) ) diff --git a/protocol/app/msgs/all_msgs.go b/protocol/app/msgs/all_msgs.go index a790f8858b..1e50883461 100644 --- a/protocol/app/msgs/all_msgs.go +++ b/protocol/app/msgs/all_msgs.go @@ -196,6 +196,10 @@ var ( "/dydxprotocol.govplus.MsgSlashValidator": {}, "/dydxprotocol.govplus.MsgSlashValidatorResponse": {}, + // listing + "/dydxprotocol.listing.MsgSetMarketsHardCap": {}, + "/dydxprotocol.listing.MsgSetMarketsHardCapResponse": {}, + // perpetuals "/dydxprotocol.perpetuals.MsgAddPremiumVotes": {}, "/dydxprotocol.perpetuals.MsgAddPremiumVotesResponse": {}, @@ -246,6 +250,10 @@ var ( "/dydxprotocol.vest.MsgDeleteVestEntry": {}, "/dydxprotocol.vest.MsgDeleteVestEntryResponse": {}, + // revshare + "/dydxprotocol.revshare.MsgSetMarketMapperRevenueShare": {}, + "/dydxprotocol.revshare.MsgSetMarketMapperRevenueShareResponse": {}, + // rewards "/dydxprotocol.rewards.MsgUpdateParams": {}, "/dydxprotocol.rewards.MsgUpdateParamsResponse": {}, diff --git a/protocol/app/msgs/internal_msgs.go b/protocol/app/msgs/internal_msgs.go index 1e6fc67d5c..437a30320c 100644 --- a/protocol/app/msgs/internal_msgs.go +++ b/protocol/app/msgs/internal_msgs.go @@ -22,9 +22,11 @@ import ( delaymsg "github.com/dydxprotocol/v4-chain/protocol/x/delaymsg/types" feetiers "github.com/dydxprotocol/v4-chain/protocol/x/feetiers/types" govplus "github.com/dydxprotocol/v4-chain/protocol/x/govplus/types" + listing "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" perpetuals "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" prices "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ratelimit "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit/types" + revshare "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" rewards "github.com/dydxprotocol/v4-chain/protocol/x/rewards/types" sending "github.com/dydxprotocol/v4-chain/protocol/x/sending/types" stats "github.com/dydxprotocol/v4-chain/protocol/x/stats/types" @@ -141,6 +143,10 @@ var ( "/dydxprotocol.govplus.MsgSlashValidator": &govplus.MsgSlashValidator{}, "/dydxprotocol.govplus.MsgSlashValidatorResponse": nil, + // listing + "/dydxprotocol.listing.MsgSetMarketsHardCap": &listing.MsgSetMarketsHardCap{}, + "/dydxprotocol.listing.MsgSetMarketsHardCapResponse": nil, + // perpetuals "/dydxprotocol.perpetuals.MsgCreatePerpetual": &perpetuals.MsgCreatePerpetual{}, "/dydxprotocol.perpetuals.MsgCreatePerpetualResponse": nil, @@ -161,6 +167,10 @@ var ( "/dydxprotocol.ratelimit.MsgSetLimitParams": &ratelimit.MsgSetLimitParams{}, "/dydxprotocol.ratelimit.MsgSetLimitParamsResponse": nil, + // revshare + "/dydxprotocol.revshare.MsgSetMarketMapperRevenueShare": &revshare.MsgSetMarketMapperRevenueShare{}, + "/dydxprotocol.revshare.MsgSetMarketMapperRevenueShareResponse": nil, + // rewards "/dydxprotocol.rewards.MsgUpdateParams": &rewards.MsgUpdateParams{}, "/dydxprotocol.rewards.MsgUpdateParamsResponse": nil, diff --git a/protocol/app/msgs/internal_msgs_test.go b/protocol/app/msgs/internal_msgs_test.go index 763706d0a7..37f55e7b80 100644 --- a/protocol/app/msgs/internal_msgs_test.go +++ b/protocol/app/msgs/internal_msgs_test.go @@ -101,6 +101,10 @@ func TestInternalMsgSamples_Gov_Key(t *testing.T) { "/dydxprotocol.govplus.MsgSlashValidator", "/dydxprotocol.govplus.MsgSlashValidatorResponse", + // listing + "/dydxprotocol.listing.MsgSetMarketsHardCap", + "/dydxprotocol.listing.MsgSetMarketsHardCapResponse", + // perpeutals "/dydxprotocol.perpetuals.MsgCreatePerpetual", "/dydxprotocol.perpetuals.MsgCreatePerpetualResponse", @@ -121,6 +125,10 @@ func TestInternalMsgSamples_Gov_Key(t *testing.T) { "/dydxprotocol.ratelimit.MsgSetLimitParams", "/dydxprotocol.ratelimit.MsgSetLimitParamsResponse", + // revshare + "/dydxprotocol.revshare.MsgSetMarketMapperRevenueShare", + "/dydxprotocol.revshare.MsgSetMarketMapperRevenueShareResponse", + // rewards "/dydxprotocol.rewards.MsgUpdateParams", "/dydxprotocol.rewards.MsgUpdateParamsResponse", diff --git a/protocol/app/process/expected_keepers.go b/protocol/app/process/expected_keepers.go index 757be839ac..88f5fddf16 100644 --- a/protocol/app/process/expected_keepers.go +++ b/protocol/app/process/expected_keepers.go @@ -2,7 +2,6 @@ package process import ( "context" - "math/big" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -40,16 +39,6 @@ type ProcessStakingKeeper interface { // ProcessPerpetualKeeper defines the expected perpetual keeper used for `ProcessProposal`. type ProcessPerpetualKeeper interface { MaybeProcessNewFundingTickEpoch(ctx sdk.Context) - GetSettlementPpm( - ctx sdk.Context, - perpetualId uint32, - quantums *big.Int, - index *big.Int, - ) ( - bigNetSettlementPpm *big.Int, - newFundingIndex *big.Int, - err error, - ) GetPerpetual(ctx sdk.Context, id uint32) (val perptypes.Perpetual, err error) } diff --git a/protocol/app/testdata/default_genesis_state.json b/protocol/app/testdata/default_genesis_state.json index 109e2e42a0..660bb86fcf 100644 --- a/protocol/app/testdata/default_genesis_state.json +++ b/protocol/app/testdata/default_genesis_state.json @@ -338,7 +338,9 @@ "port": "icahost" } }, - "listing": {}, + "listing": { + "hard_cap_for_markets": 0 + }, "params": null, "perpetuals": { "perpetuals": [], @@ -400,6 +402,13 @@ } ] }, + "revshare": { + "params": { + "address": "dydx17xpfvakm2amg962yls6f84z3kell8c5leqdyt2", + "revenue_share_ppm": 0, + "valid_days": 0 + } + }, "rewards": { "params": { "treasury_account": "rewards_treasury", diff --git a/protocol/app/upgrades/v6.0.0/constants.go b/protocol/app/upgrades/v6.0.0/constants.go index 44632c8218..52ba4eea59 100644 --- a/protocol/app/upgrades/v6.0.0/constants.go +++ b/protocol/app/upgrades/v6.0.0/constants.go @@ -4,6 +4,7 @@ import ( store "cosmossdk.io/store/types" "github.com/dydxprotocol/v4-chain/protocol/app/upgrades" listingtypes "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" + revsharetypes "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" ) const ( @@ -15,6 +16,7 @@ var Upgrade = upgrades.Upgrade{ StoreUpgrades: store.StoreUpgrades{ Added: []string{ listingtypes.StoreKey, + revsharetypes.StoreKey, }, }, } diff --git a/protocol/cmd/dydxprotocold/cmd/root.go b/protocol/cmd/dydxprotocold/cmd/root.go index f65934aacc..ec931e050f 100644 --- a/protocol/cmd/dydxprotocold/cmd/root.go +++ b/protocol/cmd/dydxprotocold/cmd/root.go @@ -2,9 +2,11 @@ package cmd import ( "errors" + "fmt" "io" "os" "path/filepath" + "time" "cosmossdk.io/client/v2/autocli" "cosmossdk.io/core/appmodule" @@ -55,6 +57,9 @@ const ( EnvPrefix = "DYDX" flagIAVLCacheSize = "iavl-cache-size" + + // TimeoutProposeOverride is the software override for the `timeout_propose` consensus parameter. + TimeoutProposeOverride = 1500 * time.Millisecond ) // NewRootCmd creates a new root command for `dydxprotocold`. It is called once in the main function. @@ -63,11 +68,18 @@ func NewRootCmd( option *RootCmdOption, homeDir string, ) *cobra.Command { + logger := log.NewLogger(os.Stdout) return NewRootCmdWithInterceptors( option, homeDir, func(serverCtxPtr *server.Context) { - + // Provide an override for `timeout_propose`. This value should be consistent across the network + // for synchrony, and should never be tweaked by individual validators in practice. + logger.Info(fmt.Sprintf( + "Overriding [consensus.timeout_propose] from %v to software constant: %v", + serverCtxPtr.Config.Consensus.TimeoutPropose, + TimeoutProposeOverride)) + serverCtxPtr.Config.Consensus.TimeoutPropose = TimeoutProposeOverride }, func(s string, appConfig *DydxAppConfig) (string, *DydxAppConfig) { return s, appConfig diff --git a/protocol/daemons/liquidation/client/sub_task_runner.go b/protocol/daemons/liquidation/client/sub_task_runner.go index 3d4130cd9f..26cad3b38d 100644 --- a/protocol/daemons/liquidation/client/sub_task_runner.go +++ b/protocol/daemons/liquidation/client/sub_task_runner.go @@ -2,7 +2,6 @@ package client import ( "context" - "math/big" "time" errorsmod "cosmossdk.io/errors" @@ -10,13 +9,10 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/daemons/flags" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" - assetstypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" - clobkeeper "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" - perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" - sakeeper "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" + salib "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/lib" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -107,7 +103,7 @@ func (c *Client) FetchApplicationStateAtBlockHeight( liqFlags flags.LiquidationFlags, ) ( subaccounts []satypes.Subaccount, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, err error, ) { defer telemetry.ModuleMeasureSince( @@ -150,7 +146,7 @@ func (c *Client) FetchApplicationStateAtBlockHeight( return l.Id }) - perpInfos = make(map[uint32]perptypes.PerpInfo, len(perpetuals)) + perpInfos = make(perptypes.PerpInfos, len(perpetuals)) for _, perp := range perpetuals { price, ok := marketPricesMap[perp.Params.MarketId] if !ok { @@ -182,7 +178,7 @@ func (c *Client) FetchApplicationStateAtBlockHeight( // at least one open position and returns a list of unique and potentially liquidatable subaccount ids. func (c *Client) GetLiquidatableSubaccountIds( subaccounts []satypes.Subaccount, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, ) ( liquidatableSubaccountIds []satypes.SubaccountId, negativeTncSubaccountIds []satypes.SubaccountId, @@ -290,7 +286,7 @@ func (c *Client) GetSubaccountOpenPositionInfo( // is not yet implemented. func (c *Client) CheckSubaccountCollateralization( unsettledSubaccount satypes.Subaccount, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, ) ( isLiquidatable bool, hasNegativeTnc bool, @@ -305,64 +301,17 @@ func (c *Client) CheckSubaccountCollateralization( // Funding payments are lazily settled, so get the settled subaccount // to ensure that the funding payments are included in the net collateral calculation. - settledSubaccount, _, err := sakeeper.GetSettledSubaccountWithPerpetuals( + settledSubaccount, _ := salib.GetSettledSubaccountWithPerpetuals( unsettledSubaccount, perpInfos, ) - if err != nil { - return false, false, err - } - bigTotalNetCollateral := big.NewInt(0) - bigTotalMaintenanceMargin := big.NewInt(0) - - // Calculate the net collateral and maintenance margin for each of the asset positions. - // Note that we only expect USDC before multi-collateral support is added. - for _, assetPosition := range settledSubaccount.AssetPositions { - if assetPosition.AssetId != assetstypes.AssetUsdc.Id { - return false, false, errorsmod.Wrapf( - assetstypes.ErrNotImplementedMulticollateral, - "Asset %d is not supported", - assetPosition.AssetId, - ) - } - // Net collateral for USDC is the quantums of the position. - // Margin requirements for USDC are zero. - bigTotalNetCollateral.Add(bigTotalNetCollateral, assetPosition.GetBigQuantums()) - } - - // Calculate the net collateral and maintenance margin for each of the perpetual positions. - for _, perpetualPosition := range settledSubaccount.PerpetualPositions { - perpInfo, ok := perpInfos[perpetualPosition.PerpetualId] - if !ok { - return false, false, errorsmod.Wrapf( - satypes.ErrPerpetualInfoDoesNotExist, - "%d", - perpetualPosition.PerpetualId, - ) - } - - bigQuantums := perpetualPosition.GetBigQuantums() - - // Get the net collateral for the position. - bigNetCollateralQuoteQuantums := perplib.GetNetNotionalInQuoteQuantums( - perpInfo.Perpetual, - perpInfo.Price, - bigQuantums, - ) - bigTotalNetCollateral.Add(bigTotalNetCollateral, bigNetCollateralQuoteQuantums) - - // Get the maintenance margin requirement for the position. - _, bigMaintenanceMarginQuoteQuantums := perplib.GetMarginRequirementsInQuoteQuantums( - perpInfo.Perpetual, - perpInfo.Price, - perpInfo.LiquidityTier, - bigQuantums, - ) - bigTotalMaintenanceMargin.Add(bigTotalMaintenanceMargin, bigMaintenanceMarginQuoteQuantums) - } + risk, err := salib.GetRiskForSubaccount( + satypes.SettledUpdate{ + SettledSubaccount: settledSubaccount, + }, + perpInfos, + ) - return clobkeeper.CanLiquidateSubaccount(bigTotalNetCollateral, bigTotalMaintenanceMargin), - bigTotalNetCollateral.Sign() == -1, - nil + return risk.IsLiquidatable(), risk.NC.Sign() < 0, nil } diff --git a/protocol/indexer/events/stateful_order.go b/protocol/indexer/events/stateful_order.go index f1a0d029db..d5cc0c537d 100644 --- a/protocol/indexer/events/stateful_order.go +++ b/protocol/indexer/events/stateful_order.go @@ -20,6 +20,23 @@ func NewLongTermOrderPlacementEvent( } } +func NewLongTermOrderReplacementEvent( + oldOrderId clobtypes.OrderId, + order clobtypes.Order, +) *StatefulOrderEventV1 { + oldIndexerOrderId := v1.OrderIdToIndexerOrderId(oldOrderId) + indexerOrder := v1.OrderToIndexerOrder(order) + orderReplace := StatefulOrderEventV1_LongTermOrderReplacementV1{ + OldOrderId: &oldIndexerOrderId, + Order: &indexerOrder, + } + return &StatefulOrderEventV1{ + Event: &StatefulOrderEventV1_OrderReplacement{ + OrderReplacement: &orderReplace, + }, + } +} + func NewStatefulOrderRemovalEvent( removedOrderId clobtypes.OrderId, reason sharedtypes.OrderRemovalReason, diff --git a/protocol/lib/ante/internal_msg.go b/protocol/lib/ante/internal_msg.go index 3ca7b1fc77..c6d4aba477 100644 --- a/protocol/lib/ante/internal_msg.go +++ b/protocol/lib/ante/internal_msg.go @@ -21,9 +21,11 @@ import ( delaymsg "github.com/dydxprotocol/v4-chain/protocol/x/delaymsg/types" feetiers "github.com/dydxprotocol/v4-chain/protocol/x/feetiers/types" govplus "github.com/dydxprotocol/v4-chain/protocol/x/govplus/types" + listing "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" perpetuals "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" prices "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ratelimit "github.com/dydxprotocol/v4-chain/protocol/x/ratelimit/types" + revshare "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" rewards "github.com/dydxprotocol/v4-chain/protocol/x/rewards/types" sending "github.com/dydxprotocol/v4-chain/protocol/x/sending/types" stats "github.com/dydxprotocol/v4-chain/protocol/x/stats/types" @@ -93,6 +95,9 @@ func IsInternalMsg(msg sdk.Msg) bool { // govplus *govplus.MsgSlashValidator, + // listing + *listing.MsgSetMarketsHardCap, + // perpetuals *perpetuals.MsgCreatePerpetual, *perpetuals.MsgSetLiquidityTier, @@ -107,6 +112,9 @@ func IsInternalMsg(msg sdk.Msg) bool { *ratelimit.MsgSetLimitParams, *ratelimit.MsgSetLimitParamsResponse, + // revshare + *revshare.MsgSetMarketMapperRevenueShare, + // rewards *rewards.MsgUpdateParams, diff --git a/protocol/lib/big_math_test.go b/protocol/lib/big_math_test.go index 45c3896c8f..e2ffe46a30 100644 --- a/protocol/lib/big_math_test.go +++ b/protocol/lib/big_math_test.go @@ -790,14 +790,6 @@ func TestBigIntRoundToMultiple(t *testing.T) { } } -func TestBigInt0(t *testing.T) { - require.Equal(t, big.NewInt(0), lib.BigInt0()) -} - -func TestBigFloat0(t *testing.T) { - require.Equal(t, big.NewFloat(0), lib.BigFloat0()) -} - func TestBigFloatMaxUint64(t *testing.T) { require.Equal(t, new(big.Float).SetUint64(math.MaxUint64), lib.BigFloatMaxUint64()) } diff --git a/protocol/lib/collections.go b/protocol/lib/collections.go index 94ffc732e6..e6f4fcebf4 100644 --- a/protocol/lib/collections.go +++ b/protocol/lib/collections.go @@ -7,6 +7,11 @@ import ( // ContainsDuplicates returns true if the slice contains duplicates, false if not. func ContainsDuplicates[V comparable](values []V) bool { + // Optimize edge case. If values is nil, len(values) returns 0. + if len(values) <= 1 { + return false + } + // Store each value as a key in the mapping. seenValues := make(map[V]struct{}, len(values)) for i, val := range values { @@ -22,6 +27,19 @@ func ContainsDuplicates[V comparable](values []V) bool { return false } +// MapToSortedSlice returns a slice of values from a map, sorted by key. +func MapToSortedSlice[R interface { + ~[]K + sort.Interface +}, K comparable, V any](m map[K]V) []V { + keys := GetSortedKeys[R](m) + values := make([]V, 0, len(m)) + for _, key := range keys { + values = append(values, m[key]) + } + return values +} + // DedupeSlice deduplicates a slice of comparable values. func DedupeSlice[V comparable](values []V) []V { seenValues := make(map[V]struct{}) diff --git a/protocol/lib/collections_test.go b/protocol/lib/collections_test.go index 7788f46222..153489fc35 100644 --- a/protocol/lib/collections_test.go +++ b/protocol/lib/collections_test.go @@ -98,6 +98,10 @@ func TestContainsDuplicates(t *testing.T) { input: []uint32{}, expected: false, }, + "One Item": { + input: []uint32{10}, + expected: false, + }, "False": { input: []uint32{1, 2, 3, 4}, expected: false, @@ -114,6 +118,59 @@ func TestContainsDuplicates(t *testing.T) { } } +func BenchmarkMapToSortedSlice(b *testing.B) { + input := map[string]string{ + "d": "4", + "b": "2", + "a": "1", + "c": "3", + "e": "5", + "f": "6", + "g": "7", + "h": "8", + "i": "9", + "j": "10", + } + for i := 0; i < b.N; i++ { + _ = lib.MapToSortedSlice[sort.StringSlice, string, string](input) + } +} + +func TestMapToSortedSlice(t *testing.T) { + tests := map[string]struct { + inputMap map[string]string + expectedResult []string + }{ + "Nil input": { + inputMap: nil, + expectedResult: []string{}, + }, + "Empty map": { + inputMap: map[string]string{}, + expectedResult: []string{}, + }, + "Single item": { + inputMap: map[string]string{"a": "1"}, + expectedResult: []string{"1"}, + }, + "Multiple items": { + inputMap: map[string]string{ + "d": "4", + "b": "2", + "a": "1", + "c": "3", + }, + expectedResult: []string{"1", "2", "3", "4"}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + actualResult := lib.MapToSortedSlice[sort.StringSlice](tc.inputMap) + require.Equal(t, tc.expectedResult, actualResult) + }) + } +} + func TestGetSortedKeys(t *testing.T) { tests := map[string]struct { inputMap map[string]string diff --git a/protocol/lib/constants.go b/protocol/lib/constants.go index 08ccf9ecab..33c67321f0 100644 --- a/protocol/lib/constants.go +++ b/protocol/lib/constants.go @@ -28,11 +28,6 @@ var PowerReduction = sdkmath.NewIntFromBigInt( new(big.Int).SetUint64(1_000_000_000_000_000_000), ) -// BigInt0 returns a `big.Int` that is set to 0. -func BigInt0() *big.Int { - return big.NewInt(0) -} - // BigNegMaxUint64 returns a `big.Int` that is set to -math.MaxUint64. func BigNegMaxUint64() *big.Int { return new(big.Int).Neg( @@ -45,11 +40,6 @@ func BigMaxInt32() *big.Int { return big.NewInt(math.MaxInt32) } -// BigFloat0 returns a `big.Float` that is set to 0. -func BigFloat0() *big.Float { - return big.NewFloat(0) -} - // BigFloatMaxUint64 returns a `big.Float` that is set to MaxUint64. func BigFloatMaxUint64() *big.Float { return new(big.Float).SetUint64(math.MaxUint64) diff --git a/protocol/lib/convert.go b/protocol/lib/convert.go index 19dbff1ef3..9c0b23f207 100644 --- a/protocol/lib/convert.go +++ b/protocol/lib/convert.go @@ -26,7 +26,7 @@ func ConvertBigFloatToUint64(value *big.Float) (uint64, error) { return 0, errors.New("value overflows uint64") } - if value.Cmp(BigFloat0()) == -1 { + if value.Sign() < 0 { return 0, errors.New("value underflows uint64") } diff --git a/protocol/lib/margin/risk.go b/protocol/lib/margin/risk.go new file mode 100644 index 0000000000..925e543c2b --- /dev/null +++ b/protocol/lib/margin/risk.go @@ -0,0 +1,61 @@ +package margin + +import ( + "math/big" +) + +// Risk is a struct to hold net collateral and margin requirements. +// This can be applied to a single position or an entire account. +type Risk struct { + MMR *big.Int // Maintenance Margin Requirement + IMR *big.Int // Initial Margin Requirement + NC *big.Int // Net Collateral +} + +// ZeroRisk returns a Risk object with all fields set to zero. +func ZeroRisk() Risk { + return Risk{ + MMR: new(big.Int), + IMR: new(big.Int), + NC: new(big.Int), + } +} + +// AddInPlace adds the values of b to a (in-place). +func (a *Risk) AddInPlace(b Risk) { + a.MMR = mustExist(a.MMR) + a.IMR = mustExist(a.IMR) + a.NC = mustExist(a.NC) + a.MMR.Add(a.MMR, mustExist(b.MMR)) + a.IMR.Add(a.IMR, mustExist(b.IMR)) + a.NC.Add(a.NC, mustExist(b.NC)) +} + +// IsInitialCollateralized returns true if the account has enough net collateral to meet the +// initial margin requirement. +func (a *Risk) IsInitialCollateralized() bool { + return a.NC.Cmp(a.IMR) >= 0 +} + +// IsMaintenanceCollateralized returns true if the account has enough net collateral to meet the +// maintenance margin requirement. +func (a *Risk) IsMaintenanceCollateralized() bool { + return a.NC.Cmp(a.MMR) >= 0 +} + +// IsLiquidatable returns true if the account is liquidatable given its maintenance margin requirement +// and net collateral. +// +// The account is liquidatable if both of the following are true: +// - The maintenance margin requirements are greater than zero (note that they can never be negative). +// - The maintenance margin requirements are greater than the account's net collateral. +func (a *Risk) IsLiquidatable() bool { + return a.MMR.Sign() > 0 && a.MMR.Cmp(a.NC) > 0 +} + +func mustExist(i *big.Int) *big.Int { + if i == nil { + return new(big.Int) + } + return i +} diff --git a/protocol/lib/margin/risk_test.go b/protocol/lib/margin/risk_test.go new file mode 100644 index 0000000000..86f3d2e7d4 --- /dev/null +++ b/protocol/lib/margin/risk_test.go @@ -0,0 +1,222 @@ +package margin_test + +import ( + "math/big" + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" + "github.com/stretchr/testify/require" +) + +func TestRisk_AddInPlace(t *testing.T) { + tests := map[string]struct { + a margin.Risk + b margin.Risk + expected margin.Risk + }{ + "zero + zero": { + a: margin.Risk{}, + b: margin.Risk{}, + expected: margin.ZeroRisk(), + }, + "zero + non-zero": { + a: margin.Risk{}, + b: margin.Risk{ + MMR: big.NewInt(100), + IMR: big.NewInt(200), + NC: big.NewInt(300), + }, + expected: margin.Risk{ + MMR: big.NewInt(100), + IMR: big.NewInt(200), + NC: big.NewInt(300), + }, + }, + "non-zero + zero": { + a: margin.Risk{ + MMR: big.NewInt(100), + IMR: big.NewInt(200), + NC: big.NewInt(300), + }, + b: margin.Risk{}, + expected: margin.Risk{ + MMR: big.NewInt(100), + IMR: big.NewInt(200), + NC: big.NewInt(300), + }, + }, + "non-zero + non-zero": { + a: margin.Risk{ + MMR: big.NewInt(100), + IMR: big.NewInt(200), + NC: big.NewInt(300), + }, + b: margin.Risk{ + MMR: big.NewInt(50), + IMR: big.NewInt(100), + NC: big.NewInt(150), + }, + expected: margin.Risk{ + MMR: big.NewInt(150), + IMR: big.NewInt(300), + NC: big.NewInt(450), + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.a.AddInPlace(tc.b) + require.Equal(t, tc.expected, tc.a) + }) + } +} + +func TestRisk_IsInitialCollateralized(t *testing.T) { + tests := map[string]struct { + NC *big.Int + IMR *big.Int + expected bool + }{ + "NC > IMR": { + NC: big.NewInt(200), + IMR: big.NewInt(100), + expected: true, + }, + "NC = IMR": { + NC: big.NewInt(100), + IMR: big.NewInt(100), + expected: true, + }, + "NC < IMR": { + NC: big.NewInt(50), + IMR: big.NewInt(100), + expected: false, + }, + "NC = 0, IMR = 0": { + NC: big.NewInt(0), + IMR: big.NewInt(0), + expected: true, + }, + "NC < 0, IMR = 0": { + NC: big.NewInt(-100), + IMR: big.NewInt(0), + expected: false, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + r := margin.Risk{ + IMR: tc.IMR, + NC: tc.NC, + } + result := r.IsInitialCollateralized() + require.Equal(t, tc.expected, result) + }) + } +} + +func TestRisk_IsMaintenanceCollateralized(t *testing.T) { + tests := map[string]struct { + NC *big.Int + MMR *big.Int + expected bool + }{ + "NC > MMR": { + NC: big.NewInt(200), + MMR: big.NewInt(100), + expected: true, + }, + "NC = MMR": { + NC: big.NewInt(100), + MMR: big.NewInt(100), + expected: true, + }, + "NC < MMR": { + NC: big.NewInt(50), + MMR: big.NewInt(100), + expected: false, + }, + "NC = 0, MMR = 0": { + NC: big.NewInt(0), + MMR: big.NewInt(0), + expected: true, + }, + "NC < 0, MMR = 0": { + NC: big.NewInt(-100), + MMR: big.NewInt(0), + expected: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + r := margin.Risk{ + MMR: tc.MMR, + NC: tc.NC, + } + result := r.IsMaintenanceCollateralized() + require.Equal(t, tc.expected, result) + }) + } +} + +func TestRisk_IsLiquidatable(t *testing.T) { + tests := map[string]struct { + NC *big.Int + MMR *big.Int + expected bool + }{ + "NC > 0, MMR = 0": { + NC: big.NewInt(100), + MMR: big.NewInt(0), + expected: false, + }, + "NC = 0, MMR > 0": { + NC: big.NewInt(0), + MMR: big.NewInt(100), + expected: true, + }, + "NC = 0, MMR = 0": { + NC: big.NewInt(0), + MMR: big.NewInt(0), + expected: false, + }, + "NC < 0, MMR > 0": { + NC: big.NewInt(-100), + MMR: big.NewInt(100), + expected: true, + }, + "NC < 0, MMR = 0": { + NC: big.NewInt(-100), + MMR: big.NewInt(0), + expected: false, + }, + "NC < MMR": { + NC: big.NewInt(75), + MMR: big.NewInt(100), + expected: true, + }, + "NC = MMR": { + NC: big.NewInt(100), + MMR: big.NewInt(100), + expected: false, + }, + "NC > MMR": { + NC: big.NewInt(125), + MMR: big.NewInt(100), + expected: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + r := margin.Risk{ + MMR: tc.MMR, + NC: tc.NC, + } + result := r.IsLiquidatable() + require.Equal(t, tc.expected, result) + }) + } +} diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index f6b5bc1e38..840229780a 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -646,16 +646,16 @@ func (_m *ClobKeeper) GetSubaccountMaxNotionalLiquidatable(ctx types.Context, su } // HandleMsgCancelOrder provides a mock function with given fields: ctx, msg -func (_m *ClobKeeper) HandleMsgCancelOrder(ctx types.Context, msg *clobtypes.MsgCancelOrder) error { - ret := _m.Called(ctx, msg) +func (_m *ClobKeeper) HandleMsgCancelOrder(ctx types.Context, msg *clobtypes.MsgCancelOrder, isInternalOrder bool) error { + ret := _m.Called(ctx, msg, isInternalOrder) if len(ret) == 0 { panic("no return value specified for HandleMsgCancelOrder") } var r0 error - if rf, ok := ret.Get(0).(func(types.Context, *clobtypes.MsgCancelOrder) error); ok { - r0 = rf(ctx, msg) + if rf, ok := ret.Get(0).(func(types.Context, *clobtypes.MsgCancelOrder, bool) error); ok { + r0 = rf(ctx, msg, isInternalOrder) } else { r0 = ret.Error(0) } diff --git a/protocol/mocks/ProcessPerpetualKeeper.go b/protocol/mocks/ProcessPerpetualKeeper.go index 12d1471c33..8296996829 100644 --- a/protocol/mocks/ProcessPerpetualKeeper.go +++ b/protocol/mocks/ProcessPerpetualKeeper.go @@ -3,8 +3,6 @@ package mocks import ( - big "math/big" - perpetualstypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" mock "github.com/stretchr/testify/mock" @@ -44,45 +42,6 @@ func (_m *ProcessPerpetualKeeper) GetPerpetual(ctx types.Context, id uint32) (pe return r0, r1 } -// GetSettlementPpm provides a mock function with given fields: ctx, perpetualId, quantums, index -func (_m *ProcessPerpetualKeeper) GetSettlementPpm(ctx types.Context, perpetualId uint32, quantums *big.Int, index *big.Int) (*big.Int, *big.Int, error) { - ret := _m.Called(ctx, perpetualId, quantums, index) - - if len(ret) == 0 { - panic("no return value specified for GetSettlementPpm") - } - - var r0 *big.Int - var r1 *big.Int - var r2 error - if rf, ok := ret.Get(0).(func(types.Context, uint32, *big.Int, *big.Int) (*big.Int, *big.Int, error)); ok { - return rf(ctx, perpetualId, quantums, index) - } - if rf, ok := ret.Get(0).(func(types.Context, uint32, *big.Int, *big.Int) *big.Int); ok { - r0 = rf(ctx, perpetualId, quantums, index) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*big.Int) - } - } - - if rf, ok := ret.Get(1).(func(types.Context, uint32, *big.Int, *big.Int) *big.Int); ok { - r1 = rf(ctx, perpetualId, quantums, index) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*big.Int) - } - } - - if rf, ok := ret.Get(2).(func(types.Context, uint32, *big.Int, *big.Int) error); ok { - r2 = rf(ctx, perpetualId, quantums, index) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - // MaybeProcessNewFundingTickEpoch provides a mock function with given fields: ctx func (_m *ProcessPerpetualKeeper) MaybeProcessNewFundingTickEpoch(ctx types.Context) { _m.Called(ctx) diff --git a/protocol/mocks/SubaccountsKeeper.go b/protocol/mocks/SubaccountsKeeper.go index e62d58044a..68d186f7a0 100644 --- a/protocol/mocks/SubaccountsKeeper.go +++ b/protocol/mocks/SubaccountsKeeper.go @@ -4,10 +4,12 @@ package mocks import ( big "math/big" - rand "math/rand" + margin "github.com/dydxprotocol/v4-chain/protocol/lib/margin" mock "github.com/stretchr/testify/mock" + rand "math/rand" + subaccountstypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" types "github.com/cosmos/cosmos-sdk/types" @@ -129,51 +131,31 @@ func (_m *SubaccountsKeeper) GetNegativeTncSubaccountSeenAtBlock(ctx types.Conte } // GetNetCollateralAndMarginRequirements provides a mock function with given fields: ctx, update -func (_m *SubaccountsKeeper) GetNetCollateralAndMarginRequirements(ctx types.Context, update subaccountstypes.Update) (*big.Int, *big.Int, *big.Int, error) { +func (_m *SubaccountsKeeper) GetNetCollateralAndMarginRequirements(ctx types.Context, update subaccountstypes.Update) (margin.Risk, error) { ret := _m.Called(ctx, update) if len(ret) == 0 { panic("no return value specified for GetNetCollateralAndMarginRequirements") } - var r0 *big.Int - var r1 *big.Int - var r2 *big.Int - var r3 error - if rf, ok := ret.Get(0).(func(types.Context, subaccountstypes.Update) (*big.Int, *big.Int, *big.Int, error)); ok { + var r0 margin.Risk + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, subaccountstypes.Update) (margin.Risk, error)); ok { return rf(ctx, update) } - if rf, ok := ret.Get(0).(func(types.Context, subaccountstypes.Update) *big.Int); ok { + if rf, ok := ret.Get(0).(func(types.Context, subaccountstypes.Update) margin.Risk); ok { r0 = rf(ctx, update) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*big.Int) - } + r0 = ret.Get(0).(margin.Risk) } - if rf, ok := ret.Get(1).(func(types.Context, subaccountstypes.Update) *big.Int); ok { + if rf, ok := ret.Get(1).(func(types.Context, subaccountstypes.Update) error); ok { r1 = rf(ctx, update) } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*big.Int) - } - } - - if rf, ok := ret.Get(2).(func(types.Context, subaccountstypes.Update) *big.Int); ok { - r2 = rf(ctx, update) - } else { - if ret.Get(2) != nil { - r2 = ret.Get(2).(*big.Int) - } - } - - if rf, ok := ret.Get(3).(func(types.Context, subaccountstypes.Update) error); ok { - r3 = rf(ctx, update) - } else { - r3 = ret.Error(3) + r1 = ret.Error(1) } - return r0, r1, r2, r3 + return r0, r1 } // GetRandomSubaccount provides a mock function with given fields: ctx, _a1 diff --git a/protocol/scripts/genesis/sample_pregenesis.json b/protocol/scripts/genesis/sample_pregenesis.json index 912cf6d4c1..04de949584 100644 --- a/protocol/scripts/genesis/sample_pregenesis.json +++ b/protocol/scripts/genesis/sample_pregenesis.json @@ -855,7 +855,9 @@ "port": "icahost" } }, - "listing": {}, + "listing": { + "hard_cap_for_markets": 0 + }, "params": null, "perpetuals": { "liquidity_tiers": [ @@ -1751,6 +1753,13 @@ } ] }, + "revshare": { + "params": { + "address": "dydx17xpfvakm2amg962yls6f84z3kell8c5leqdyt2", + "revenue_share_ppm": 0, + "valid_days": 0 + } + }, "rewards": { "params": { "denom": "asample", diff --git a/protocol/streaming/grpc/grpc_streaming_manager.go b/protocol/streaming/grpc/grpc_streaming_manager.go index 6f447b755a..e45275647f 100644 --- a/protocol/streaming/grpc/grpc_streaming_manager.go +++ b/protocol/streaming/grpc/grpc_streaming_manager.go @@ -9,7 +9,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/proto" ocutypes "github.com/dydxprotocol/v4-chain/protocol/indexer/off_chain_updates/types" - "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" "github.com/dydxprotocol/v4-chain/protocol/streaming/grpc/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -205,11 +204,12 @@ func (sm *GrpcStreamingManagerImpl) Stop() { sm.done <- true } -// SendSnapshot groups updates by their clob pair ids and -// sends messages to the subscribers. It groups out updates differently -// and bypasses the buffer. +// SendSnapshot sends messages to a particular subscriber without buffering. +// Note this method requires the lock and assumes that the lock has already been +// acquired by the caller. func (sm *GrpcStreamingManagerImpl) SendSnapshot( offchainUpdates *clobtypes.OffchainUpdates, + subscriptionId uint32, blockHeight uint32, execMode sdk.ExecMode, ) { @@ -219,74 +219,56 @@ func (sm *GrpcStreamingManagerImpl) SendSnapshot( time.Now(), ) - // Group updates by clob pair id. - updates := make(map[uint32]*clobtypes.OffchainUpdates) - for _, message := range offchainUpdates.Messages { - clobPairId := message.OrderId.ClobPairId - if _, ok := updates[clobPairId]; !ok { - updates[clobPairId] = clobtypes.NewOffchainUpdates() - } - updates[clobPairId].Messages = append(updates[clobPairId].Messages, message) + v1updates, err := GetOffchainUpdatesV1(offchainUpdates) + if err != nil { + panic(err) } - // Unmarshal each per-clob pair message to v1 updates. - updatesByClobPairId := make(map[uint32][]ocutypes.OffChainUpdateV1) - for clobPairId, update := range updates { - v1updates, err := GetOffchainUpdatesV1(update) - if err != nil { - panic(err) - } - updatesByClobPairId[clobPairId] = v1updates - } - - sm.Lock() - defer sm.Unlock() - - idsToRemove := make([]uint32, 0) - for id, subscription := range sm.orderbookSubscriptions { - // Consolidate orderbook updates into a single `StreamUpdate`. - v1updates := make([]ocutypes.OffChainUpdateV1, 0) - for _, clobPairId := range subscription.clobPairIds { - if update, ok := updatesByClobPairId[clobPairId]; ok { - v1updates = append(v1updates, update...) - } + removeSubscription := false + if len(v1updates) > 0 { + subscription, ok := sm.orderbookSubscriptions[subscriptionId] + if !ok { + sm.logger.Error( + fmt.Sprintf( + "GRPC Streaming subscription id %+v not found. This should not happen.", + subscriptionId, + ), + ) + return } - - if len(v1updates) > 0 { - streamUpdates := []clobtypes.StreamUpdate{ - { - UpdateMessage: &clobtypes.StreamUpdate_OrderbookUpdate{ - OrderbookUpdate: &clobtypes.StreamOrderbookUpdate{ - Updates: v1updates, - Snapshot: true, - }, + streamUpdates := []clobtypes.StreamUpdate{ + { + UpdateMessage: &clobtypes.StreamUpdate_OrderbookUpdate{ + OrderbookUpdate: &clobtypes.StreamOrderbookUpdate{ + Updates: v1updates, + Snapshot: true, }, - BlockHeight: blockHeight, - ExecMode: uint32(execMode), }, - } - metrics.IncrCounter( - metrics.GrpcAddToSubscriptionChannelCount, - 1, + BlockHeight: blockHeight, + ExecMode: uint32(execMode), + }, + } + metrics.IncrCounter( + metrics.GrpcAddToSubscriptionChannelCount, + 1, + ) + select { + case subscription.updatesChannel <- streamUpdates: + default: + sm.logger.Error( + fmt.Sprintf( + "GRPC Streaming subscription id %+v channel full capacity. Dropping subscription connection.", + subscriptionId, + ), ) - select { - case subscription.updatesChannel <- streamUpdates: - default: - sm.logger.Error( - fmt.Sprintf( - "GRPC Streaming subscription id %+v channel full capacity. Dropping subscription connection.", - id, - ), - ) - idsToRemove = append(idsToRemove, subscription.subscriptionId) - } + removeSubscription = true } } // Clean up subscriptions that have been closed. // If a Send update has failed for any clob pair id, the whole subscription will be removed. - for _, id := range idsToRemove { - sm.removeSubscription(id) + if removeSubscription { + sm.removeSubscription(subscriptionId) } } @@ -404,17 +386,22 @@ func (sm *GrpcStreamingManagerImpl) AddUpdatesToCache( sm.EmitMetrics() } -// FlushStreamUpdates takes in a map of clob pair id to stream updates and emits them to subscribers. func (sm *GrpcStreamingManagerImpl) FlushStreamUpdates() { + sm.Lock() + defer sm.Unlock() + sm.FlushStreamUpdatesWithLock() +} + +// FlushStreamUpdatesWithLock takes in a map of clob pair id to stream updates and emits them to subscribers. +// Note this method requires the lock and assumes that the lock has already been +// acquired by the caller. +func (sm *GrpcStreamingManagerImpl) FlushStreamUpdatesWithLock() { defer metrics.ModuleMeasureSince( metrics.FullNodeGrpc, metrics.GrpcFlushUpdatesLatency, time.Now(), ) - sm.Lock() - defer sm.Unlock() - // Non-blocking send updates through subscriber's buffered channel. // If the buffer is full, drop the subscription. idsToRemove := make([]uint32, 0) @@ -455,23 +442,34 @@ func (sm *GrpcStreamingManagerImpl) FlushStreamUpdates() { sm.EmitMetrics() } -// GetUninitializedClobPairIds returns the clob pair ids that have not been initialized. -func (sm *GrpcStreamingManagerImpl) GetUninitializedClobPairIds() []uint32 { +func (sm *GrpcStreamingManagerImpl) InitializeNewGrpcStreams( + getOrderbookSnapshot func(clobPairId clobtypes.ClobPairId) *clobtypes.OffchainUpdates, + blockHeight uint32, + execMode sdk.ExecMode, +) { sm.Lock() defer sm.Unlock() - clobPairIds := make(map[uint32]bool) - for _, subscription := range sm.orderbookSubscriptions { + // Flush any pending updates before sending the snapshot to avoid + // race conditions with the snapshot. + sm.FlushStreamUpdatesWithLock() + + updatesByClobPairId := make(map[uint32]*clobtypes.OffchainUpdates) + for subscriptionId, subscription := range sm.orderbookSubscriptions { subscription.initialize.Do( func() { + allUpdates := clobtypes.NewOffchainUpdates() for _, clobPairId := range subscription.clobPairIds { - clobPairIds[clobPairId] = true + if _, ok := updatesByClobPairId[clobPairId]; !ok { + updatesByClobPairId[clobPairId] = getOrderbookSnapshot(clobtypes.ClobPairId(clobPairId)) + } + allUpdates.Append(updatesByClobPairId[clobPairId]) } + + sm.SendSnapshot(allUpdates, subscriptionId, blockHeight, execMode) }, ) } - - return lib.GetSortedKeys[lib.Sortable[uint32]](clobPairIds) } // GetOffchainUpdatesV1 unmarshals messages in offchain updates to OffchainUpdateV1. diff --git a/protocol/streaming/grpc/noop_streaming_manager.go b/protocol/streaming/grpc/noop_streaming_manager.go index 0875e89faa..f5c61f0713 100644 --- a/protocol/streaming/grpc/noop_streaming_manager.go +++ b/protocol/streaming/grpc/noop_streaming_manager.go @@ -29,6 +29,7 @@ func (sm *NoopGrpcStreamingManager) Subscribe( func (sm *NoopGrpcStreamingManager) SendSnapshot( updates *clobtypes.OffchainUpdates, + subscriptionId uint32, blockHeight uint32, execMode sdk.ExecMode, ) { @@ -49,8 +50,11 @@ func (sm *NoopGrpcStreamingManager) SendOrderbookFillUpdates( ) { } -func (sm *NoopGrpcStreamingManager) GetUninitializedClobPairIds() []uint32 { - return []uint32{} +func (sm *NoopGrpcStreamingManager) InitializeNewGrpcStreams( + getOrderbookSnapshot func(clobPairId clobtypes.ClobPairId) *clobtypes.OffchainUpdates, + blockHeight uint32, + execMode sdk.ExecMode, +) { } func (sm *NoopGrpcStreamingManager) Stop() { diff --git a/protocol/streaming/grpc/types/manager.go b/protocol/streaming/grpc/types/manager.go index ec43821093..74b145985c 100644 --- a/protocol/streaming/grpc/types/manager.go +++ b/protocol/streaming/grpc/types/manager.go @@ -15,9 +15,14 @@ type GrpcStreamingManager interface { ) ( err error, ) - GetUninitializedClobPairIds() []uint32 + InitializeNewGrpcStreams( + getOrderbookSnapshot func(clobPairId clobtypes.ClobPairId) *clobtypes.OffchainUpdates, + blockHeight uint32, + execMode sdk.ExecMode, + ) SendSnapshot( offchainUpdates *clobtypes.OffchainUpdates, + subscriptionId uint32, blockHeight uint32, execMode sdk.ExecMode, ) diff --git a/protocol/testing/containertest/preupgrade_genesis.json b/protocol/testing/containertest/preupgrade_genesis.json index 9821fdcf53..7aad403879 100644 --- a/protocol/testing/containertest/preupgrade_genesis.json +++ b/protocol/testing/containertest/preupgrade_genesis.json @@ -2178,6 +2178,7 @@ } ] }, + "revshare": {}, "rewards": { "params": { "treasury_account": "rewards_treasury", diff --git a/protocol/testutil/constants/genesis.go b/protocol/testutil/constants/genesis.go index 3155ecbe51..06e260282c 100644 --- a/protocol/testutil/constants/genesis.go +++ b/protocol/testutil/constants/genesis.go @@ -871,7 +871,9 @@ const GenesisState = `{ } } }, - "listing": {}, + "listing": { + "hard_cap_for_markets": 0 + }, "perpetuals": { "liquidity_tiers": [ { @@ -1348,6 +1350,13 @@ const GenesisState = `{ } ] }, + "revshare": { + "params": { + "address": "dydx17xpfvakm2amg962yls6f84z3kell8c5leqdyt2", + "revenue_share_ppm": 0, + "valid_days": 0 + } + }, "rewards": { "params": { "treasury_account":"rewards_treasury", diff --git a/protocol/testutil/keeper/vault.go b/protocol/testutil/keeper/vault.go index cbcc87e5d6..5a82da7a61 100644 --- a/protocol/testutil/keeper/vault.go +++ b/protocol/testutil/keeper/vault.go @@ -58,6 +58,7 @@ func createVaultKeeper( &mocks.PricesKeeper{}, &mocks.SendingKeeper{}, &mocks.SubaccountsKeeper{}, + &mocks.IndexerEventManager{}, []string{ lib.GovModuleAddress.String(), delaymsgtypes.ModuleAddress.String(), diff --git a/protocol/x/assets/keeper/asset.go b/protocol/x/assets/keeper/asset.go index 8910a34299..efeb3af667 100644 --- a/protocol/x/assets/keeper/asset.go +++ b/protocol/x/assets/keeper/asset.go @@ -182,75 +182,6 @@ func (k Keeper) GetAllAssets( return list } -// GetNetCollateral returns the net collateral that a given position (quantums) -// for a given assetId contributes to an account. -func (k Keeper) GetNetCollateral( - ctx sdk.Context, - id uint32, - bigQuantums *big.Int, -) ( - bigNetCollateralQuoteQuantums *big.Int, - err error, -) { - if id == types.AssetUsdc.Id { - return new(big.Int).Set(bigQuantums), nil - } - - // Get asset - _, exists := k.GetAsset(ctx, id) - if !exists { - return big.NewInt(0), errorsmod.Wrap(types.ErrAssetDoesNotExist, lib.UintToString(id)) - } - - // Balance is zero. - if bigQuantums.BitLen() == 0 { - return big.NewInt(0), nil - } - - // Balance is positive. - // TODO(DEC-581): add multi-collateral support. - if bigQuantums.Sign() == 1 { - return big.NewInt(0), types.ErrNotImplementedMulticollateral - } - - // Balance is negative. - // TODO(DEC-582): add margin-trading support. - return big.NewInt(0), types.ErrNotImplementedMargin -} - -// GetMarginRequirements returns the initial and maintenance margin- -// requirements for a given position size for a given assetId. -func (k Keeper) GetMarginRequirements( - ctx sdk.Context, - id uint32, - bigQuantums *big.Int, -) ( - bigInitialMarginQuoteQuantums *big.Int, - bigMaintenanceMarginQuoteQuantums *big.Int, - err error, -) { - // QuoteBalance does not contribute to any margin requirements. - if id == types.AssetUsdc.Id { - return big.NewInt(0), big.NewInt(0), nil - } - - // Get asset - _, exists := k.GetAsset(ctx, id) - if !exists { - return big.NewInt(0), big.NewInt(0), errorsmod.Wrap( - types.ErrAssetDoesNotExist, lib.UintToString(id)) - } - - // Balance is zero or positive. - if bigQuantums.Sign() >= 0 { - return big.NewInt(0), big.NewInt(0), nil - } - - // Balance is negative. - // TODO(DEC-582): margin-trading - return big.NewInt(0), big.NewInt(0), types.ErrNotImplementedMargin -} - // ConvertAssetToCoin converts the given `assetId` and `quantums` used in `x/asset`, // to an `sdk.Coin` in correspoding `denom` and `amount` used in `x/bank`. // Also outputs `convertedQuantums` which has the equal value as converted `sdk.Coin`. diff --git a/protocol/x/assets/keeper/asset_test.go b/protocol/x/assets/keeper/asset_test.go index ae6a62999c..554dd3c6e7 100644 --- a/protocol/x/assets/keeper/asset_test.go +++ b/protocol/x/assets/keeper/asset_test.go @@ -13,6 +13,7 @@ import ( keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" "github.com/dydxprotocol/v4-chain/protocol/testutil/nullify" "github.com/dydxprotocol/v4-chain/protocol/x/assets/keeper" + assetslib "github.com/dydxprotocol/v4-chain/protocol/x/assets/lib" "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" priceskeeper "github.com/dydxprotocol/v4-chain/protocol/x/prices/keeper" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" @@ -318,63 +319,87 @@ func TestGetAllAssets_Success(t *testing.T) { ) } -func TestGetNetCollateral(t *testing.T) { - ctx, keeper, pricesKeeper, _, _, _ := keepertest.AssetsKeepers(t, true) - _, err := createNAssets(t, ctx, keeper, pricesKeeper, 2) - require.NoError(t, err) - - netCollateral, err := keeper.GetNetCollateral( - ctx, - types.AssetUsdc.Id, - new(big.Int).SetInt64(100), - ) - require.NoError(t, err) - require.Equal(t, new(big.Int).SetInt64(100), netCollateral) - - _, err = keeper.GetNetCollateral( - ctx, - uint32(1), - new(big.Int).SetInt64(100), - ) - require.EqualError(t, types.ErrNotImplementedMulticollateral, err.Error()) - - _, err = keeper.GetNetCollateral( - ctx, - uint32(1), - new(big.Int).SetInt64(-100), - ) - require.EqualError(t, types.ErrNotImplementedMargin, err.Error()) -} +func TestGetNetCollateralAndMarginRequirements(t *testing.T) { + tests := map[string]struct { + assetId uint32 + bigQuantums *big.Int + expectedNC *big.Int + expectedIMR *big.Int + expectedMMR *big.Int + expectedErr error + }{ + "USDC asset. Positive Balance": { + assetId: types.AssetUsdc.Id, + bigQuantums: big.NewInt(100), + expectedNC: big.NewInt(100), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + "USDC asset. Negative Balance": { + assetId: types.AssetUsdc.Id, + bigQuantums: big.NewInt(-100), + expectedNC: big.NewInt(-100), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + "USDC asset. Zero Balance": { + assetId: types.AssetUsdc.Id, + bigQuantums: big.NewInt(0), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + "Non USDC asset. Positive Balance": { + assetId: uint32(1), + bigQuantums: big.NewInt(100), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: types.ErrNotImplementedMulticollateral, + }, + "Non USDC asset. Negative Balance": { + assetId: uint32(1), + bigQuantums: big.NewInt(-100), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: types.ErrNotImplementedMargin, + }, + "Non USDC asset. Zero Balance": { + assetId: uint32(1), + bigQuantums: big.NewInt(0), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + } -func TestGetMarginRequirements(t *testing.T) { - ctx, keeper, pricesKeeper, _, _, _ := keepertest.AssetsKeepers(t, true) - _, err := createNAssets(t, ctx, keeper, pricesKeeper, 2) - require.NoError(t, err) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx, keeper, pricesKeeper, _, _, _ := keepertest.AssetsKeepers(t, true) + _, err := createNAssets(t, ctx, keeper, pricesKeeper, 2) + require.NoError(t, err) - initial, maintenance, err := keeper.GetMarginRequirements( - ctx, - types.AssetUsdc.Id, - new(big.Int).SetInt64(100), - ) - require.NoError(t, err) - require.Equal(t, new(big.Int), initial) - require.Equal(t, new(big.Int), maintenance) + risk, err := assetslib.GetNetCollateralAndMarginRequirements( + tc.assetId, + tc.bigQuantums, + ) - initial, maintenance, err = keeper.GetMarginRequirements( - ctx, - uint32(1), - new(big.Int).SetInt64(100), - ) - require.NoError(t, err) - require.Equal(t, new(big.Int), initial) - require.Equal(t, new(big.Int), maintenance) + require.Equal(t, tc.expectedNC, risk.NC) + require.Equal(t, tc.expectedIMR, risk.IMR) + require.Equal(t, tc.expectedMMR, risk.MMR) - _, _, err = keeper.GetMarginRequirements( - ctx, - uint32(1), - new(big.Int).SetInt64(-100), - ) - require.EqualError(t, types.ErrNotImplementedMargin, err.Error()) + if tc.expectedErr != nil { + require.ErrorIs(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + } + }) + } } func TestConvertAssetToCoin_Success(t *testing.T) { diff --git a/protocol/x/assets/lib/lib.go b/protocol/x/assets/lib/lib.go new file mode 100644 index 0000000000..cceb8dc525 --- /dev/null +++ b/protocol/x/assets/lib/lib.go @@ -0,0 +1,41 @@ +package lib + +import ( + "math/big" + + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" + "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" +) + +// GetNetCollateralAndMarginRequirements returns the net collateral, initial margin, and maintenance margin +// that a given position (quantums) for a given assetId contributes to an account. +func GetNetCollateralAndMarginRequirements( + id uint32, + bigQuantums *big.Int, +) ( + risk margin.Risk, + err error, +) { + risk = margin.ZeroRisk() + + // Balance is zero. + if bigQuantums.BitLen() == 0 { + return risk, nil + } + + // USDC. + if id == types.AssetUsdc.Id { + risk.NC = new(big.Int).Set(bigQuantums) + return risk, nil + } + + // Balance is positive. + // TODO(DEC-581): add multi-collateral support. + if bigQuantums.Sign() == 1 { + return risk, types.ErrNotImplementedMulticollateral + } + + // Balance is negative. + // TODO(DEC-582): margin-trading + return risk, types.ErrNotImplementedMargin +} diff --git a/protocol/x/assets/lib/lib_test.go b/protocol/x/assets/lib/lib_test.go new file mode 100644 index 0000000000..04b7233154 --- /dev/null +++ b/protocol/x/assets/lib/lib_test.go @@ -0,0 +1,89 @@ +package lib_test + +import ( + "math/big" + "testing" + + assetslib "github.com/dydxprotocol/v4-chain/protocol/x/assets/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + "github.com/stretchr/testify/require" +) + +func TestGetNetCollateralAndMarginRequirements(t *testing.T) { + tests := map[string]struct { + assetId uint32 + bigQuantums *big.Int + expectedNC *big.Int + expectedIMR *big.Int + expectedMMR *big.Int + expectedErr error + }{ + "USDC asset. Positive Balance": { + assetId: types.AssetUsdc.Id, + bigQuantums: big.NewInt(100), + expectedNC: big.NewInt(100), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + "USDC asset. Negative Balance": { + assetId: types.AssetUsdc.Id, + bigQuantums: big.NewInt(-100), + expectedNC: big.NewInt(-100), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + "USDC asset. Zero Balance": { + assetId: types.AssetUsdc.Id, + bigQuantums: big.NewInt(0), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + "Non USDC asset. Positive Balance": { + assetId: uint32(1), + bigQuantums: big.NewInt(100), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: types.ErrNotImplementedMulticollateral, + }, + "Non USDC asset. Negative Balance": { + assetId: uint32(1), + bigQuantums: big.NewInt(-100), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: types.ErrNotImplementedMargin, + }, + "Non USDC asset. Zero Balance": { + assetId: uint32(1), + bigQuantums: big.NewInt(0), + expectedNC: big.NewInt(0), + expectedIMR: big.NewInt(0), + expectedMMR: big.NewInt(0), + expectedErr: nil, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + risk, err := assetslib.GetNetCollateralAndMarginRequirements( + tc.assetId, + tc.bigQuantums, + ) + + require.Equal(t, tc.expectedNC, risk.NC) + require.Equal(t, tc.expectedIMR, risk.IMR) + require.Equal(t, tc.expectedMMR, risk.MMR) + + if tc.expectedErr != nil { + require.ErrorIs(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/protocol/x/clob/keeper/deleveraging.go b/protocol/x/clob/keeper/deleveraging.go index 3ba9bc4870..c088887747 100644 --- a/protocol/x/clob/keeper/deleveraging.go +++ b/protocol/x/clob/keeper/deleveraging.go @@ -178,10 +178,7 @@ func (k Keeper) CanDeleverageSubaccount( subaccountId satypes.SubaccountId, perpetualId uint32, ) (shouldDeleverageAtBankruptcyPrice bool, shouldDeleverageAtOraclePrice bool, err error) { - bigNetCollateral, - _, - _, - err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + risk, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: subaccountId}, ) @@ -190,7 +187,7 @@ func (k Keeper) CanDeleverageSubaccount( } // Negative TNC, deleverage at bankruptcy price. - if bigNetCollateral.Sign() == -1 { + if risk.NC.Sign() == -1 { return true, false, nil } @@ -226,10 +223,7 @@ func (k Keeper) GateWithdrawalsIfNegativeTncSubaccountSeen( foundNegativeTncSubaccount := false var negativeTncSubaccountId satypes.SubaccountId for _, subaccountId := range negativeTncSubaccountIds { - bigNetCollateral, - _, - _, - err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + risk, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: subaccountId}, ) @@ -238,7 +232,7 @@ func (k Keeper) GateWithdrawalsIfNegativeTncSubaccountSeen( } // If the subaccount has negative TNC, mark that a negative TNC subaccount was found. - if bigNetCollateral.Sign() == -1 { + if risk.NC.Sign() == -1 { foundNegativeTncSubaccount = true negativeTncSubaccountId = subaccountId break diff --git a/protocol/x/clob/keeper/equity_tier_limit.go b/protocol/x/clob/keeper/equity_tier_limit.go index 63ae1ba9e8..380f832105 100644 --- a/protocol/x/clob/keeper/equity_tier_limit.go +++ b/protocol/x/clob/keeper/equity_tier_limit.go @@ -62,7 +62,7 @@ func (k Keeper) getEquityTierLimitForSubaccount( ctx sdk.Context, subaccountId satypes.SubaccountId, equityTierLimits []types.EquityTierLimit, ) (equityTier types.EquityTierLimit, bigNetCollateral *big.Int, err error) { - netCollateral, _, _, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + risk, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{ SubaccountId: subaccountId, @@ -74,7 +74,7 @@ func (k Keeper) getEquityTierLimitForSubaccount( var equityTierLimit types.EquityTierLimit for _, limit := range equityTierLimits { - if netCollateral.Cmp(limit.UsdTncRequired.BigInt()) < 0 { + if risk.NC.Cmp(limit.UsdTncRequired.BigInt()) < 0 { break } equityTierLimit = limit @@ -87,7 +87,7 @@ func (k Keeper) getEquityTierLimitForSubaccount( "Opening order would exceed equity tier limit of %d, for subaccount %+v with net collateral %+v", equityTierLimit.Limit, subaccountId, - netCollateral, + risk.NC, ) } diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 5a31a0fa19..4ea1edee3a 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -259,20 +259,14 @@ func (k *Keeper) SetAnteHandler(anteHandler sdk.AnteHandler) { // by sending the corresponding orderbook snapshots. func (k Keeper) InitializeNewGrpcStreams(ctx sdk.Context) { streamingManager := k.GetGrpcStreamingManager() - allUpdates := types.NewOffchainUpdates() - uninitializedClobPairIds := streamingManager.GetUninitializedClobPairIds() - for _, clobPairId := range uninitializedClobPairIds { - update := k.MemClob.GetOffchainUpdatesForOrderbookSnapshot( - ctx, - types.ClobPairId(clobPairId), - ) - - allUpdates.Append(update) - } - - streamingManager.SendSnapshot( - allUpdates, + streamingManager.InitializeNewGrpcStreams( + func(clobPairId types.ClobPairId) *types.OffchainUpdates { + return k.MemClob.GetOffchainUpdatesForOrderbookSnapshot( + ctx, + clobPairId, + ) + }, lib.MustConvertIntegerToUint32(ctx.BlockHeight()), ctx.ExecMode(), ) diff --git a/protocol/x/clob/keeper/liquidations.go b/protocol/x/clob/keeper/liquidations.go index 838b7c6f51..725cab4cd7 100644 --- a/protocol/x/clob/keeper/liquidations.go +++ b/protocol/x/clob/keeper/liquidations.go @@ -354,10 +354,7 @@ func (k Keeper) IsLiquidatable( bool, error, ) { - bigNetCollateral, - _, - bigMaintenanceMargin, - err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + risk, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: subaccountId}, ) @@ -365,22 +362,7 @@ func (k Keeper) IsLiquidatable( return false, err } - return CanLiquidateSubaccount(bigNetCollateral, bigMaintenanceMargin), nil -} - -// CanLiquidateSubaccount returns true if a subaccount is liquidatable given its total net collateral and -// maintenance margin requirement. -// -// The subaccount is liquidatable if both of the following are true: -// - The maintenance margin requirements are greater than zero (note that they can never be negative). -// - The maintenance margin requirements are greater than the subaccount's net collateral. -// -// Note that this is a stateless function. -func CanLiquidateSubaccount( - bigNetCollateral *big.Int, - bigMaintenanceMargin *big.Int, -) bool { - return bigMaintenanceMargin.Sign() > 0 && bigMaintenanceMargin.Cmp(bigNetCollateral) == 1 + return risk.IsLiquidatable(), nil } // EnsureIsLiquidatable returns an error if the subaccount is not liquidatable. @@ -430,7 +412,7 @@ func (k Keeper) GetBankruptcyPriceInQuoteQuantums( // - DMMR (delta maintenance margin requirement). // - TMMR (total maintenance margin requirement). - tncBig, _, tmmrBig, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + riskTotal, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: subaccountId}, ) @@ -469,13 +451,13 @@ func (k Keeper) GetBankruptcyPriceInQuoteQuantums( // with a position size of `PS + deltaQuantums`. // Note that we are intentionally not calculating `DNNV` from `deltaQuantums` // directly to avoid rounding errors. - pnnvBig, _, pmmrBig := perplib.GetNetCollateralAndMarginRequirements( + riskPosOld := perplib.GetNetCollateralAndMarginRequirements( perpetual, marketPrice, liquidityTier, psBig, ) - pnnvadBig, _, pmmradBig := perplib.GetNetCollateralAndMarginRequirements( + riskPosNew := perplib.GetNetCollateralAndMarginRequirements( perpetual, marketPrice, liquidityTier, @@ -485,26 +467,26 @@ func (k Keeper) GetBankruptcyPriceInQuoteQuantums( // with a position size of `PS + deltaQuantums`. // Note that we cannot directly calculate `DMMR` from `deltaQuantums` because the maintenance // margin requirement function could be non-linear. - dnnvBig := new(big.Int).Sub(pnnvadBig, pnnvBig) - dmmrBig := new(big.Int).Sub(pmmradBig, pmmrBig) - // `dmmrBig` should never be positive if `| PS | >= | PS + deltaQuantums |`. If it is, panic. - if dmmrBig.Sign() == 1 { + deltaNC := new(big.Int).Sub(riskPosNew.NC, riskPosOld.NC) + deltaMMR := new(big.Int).Sub(riskPosNew.MMR, riskPosOld.MMR) + // `deltaMMR` should never be positive if `| PS | >= | PS + deltaQuantums |`. If it is, panic. + if deltaMMR.Sign() == 1 { panic("GetBankruptcyPriceInQuoteQuantums: DMMR is positive") } // Calculate `TNC * abs(DMMR) / TMMR`. - tncMulDmmrBig := new(big.Int).Mul(tncBig, new(big.Int).Abs(dmmrBig)) + tncMulDmmrBig := new(big.Int).Mul(riskTotal.NC, new(big.Int).Abs(deltaMMR)) // This calculation is intentionally rounded down to negative infinity to ensure the // final result is rounded towards positive-infinity. This works because of the following: // - This is the only division in the equation. // - This calculation is subtracted from `-DNNV` to get the final result. // - The dividend `TNC * abs(DMMR)` is the only number that can be negative, and `Div` uses // Euclidean division so even if `TNC < 0` this will still round towards negative infinity. - quoteQuantumsBeforeBankruptcyBig := new(big.Int).Div(tncMulDmmrBig, tmmrBig) + quoteQuantumsBeforeBankruptcyBig := new(big.Int).Div(tncMulDmmrBig, riskTotal.MMR) // Calculate `-DNNV - TNC * abs(DMMR) / TMMR`. bankruptcyPriceQuoteQuantumsBig := new(big.Int).Sub( - new(big.Int).Neg(dnnvBig), + new(big.Int).Neg(deltaNC), quoteQuantumsBeforeBankruptcyBig, ) @@ -559,17 +541,14 @@ func (k Keeper) GetFillablePrice( return nil, err } - pnnvBig, _, pmmrBig := perplib.GetNetCollateralAndMarginRequirements( + riskPos := perplib.GetNetCollateralAndMarginRequirements( perpetual, marketPrice, liquidityTier, psBig, ) - tncBig, - _, - tmmrBig, - err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + riskTotal, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: subaccountId}, ) @@ -579,7 +558,7 @@ func (k Keeper) GetFillablePrice( // stat liquidation order for negative TNC // TODO(CLOB-906) Prevent duplicated stat emissions for liquidation orders in PrepareCheckState. - if tncBig.Sign() < 0 { + if riskTotal.NC.Sign() < 0 { callback := metrics.PrepareCheckState if !ctx.IsCheckTx() && !ctx.IsReCheckTx() { callback = metrics.DeliverTx @@ -601,7 +580,7 @@ func (k Keeper) GetFillablePrice( ctx.Logger().Info( "GetFillablePrice: Subaccount has negative TNC. SubaccountId: %+v, TNC: %+v", subaccountId, - tncBig, + riskTotal.NC, ) } @@ -610,7 +589,7 @@ func (k Keeper) GetFillablePrice( smmr := liquidationsConfig.FillablePriceConfig.SpreadToMaintenanceMarginRatioPpm // Calculate the ABR (adjusted bankruptcy rating). - tncDivTmmrRat := new(big.Rat).SetFrac(tncBig, tmmrBig) + tncDivTmmrRat := new(big.Rat).SetFrac(riskTotal.NC, riskTotal.MMR) unboundedAbrRat := lib.BigRatMulPpm( new(big.Rat).Sub( lib.BigRat1(), @@ -624,7 +603,7 @@ func (k Keeper) GetFillablePrice( // Calculate `SMMR * PMMR` (the maximum liquidation spread in quote quantums). maxLiquidationSpreadQuoteQuantumsRat := lib.BigRatMulPpm( - new(big.Rat).SetInt(pmmrBig), + new(big.Rat).SetInt(riskPos.MMR), smmr, ) @@ -636,7 +615,7 @@ func (k Keeper) GetFillablePrice( // For shorts, `pnnvRat < 0` meaning the fillable price in quote quantums will be higher than // the oracle price (in this case the result will be negative, but dividing by `positionSize` below // will make it positive since `positionSize < 0` for shorts). - pnnvRat := new(big.Rat).SetInt(pnnvBig) + pnnvRat := new(big.Rat).SetInt(riskPos.NC) fillablePriceQuoteQuantumsRat := new(big.Rat).Sub(pnnvRat, fillablePriceOracleDeltaQuoteQuantumsRat) // Calculate the fillable price by dividing by `PS`. diff --git a/protocol/x/clob/keeper/mev.go b/protocol/x/clob/keeper/mev.go index 4f6c592209..b4a283c970 100644 --- a/protocol/x/clob/keeper/mev.go +++ b/protocol/x/clob/keeper/mev.go @@ -13,6 +13,7 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" "github.com/dydxprotocol/v4-chain/protocol/x/clob/mev_telemetry" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -786,16 +787,15 @@ func (k Keeper) AddSettlementForPositionDelta( } // Get the funding payment for this position delta. - bigNetSettlementPpm, _, err := perpetualKeeper.GetSettlementPpm( - ctx, - perpetualId, - deltaQuantums, - // Use the position's old funding index to calculate the funding payment. - fundingIndex, - ) + perpetual, err := perpetualKeeper.GetPerpetual(ctx, perpetualId) if err != nil { return err } + bigNetSettlementPpm, _ := perplib.GetSettlementPpmWithPerpetual( + perpetual, + deltaQuantums, + fundingIndex, + ) // Add the settlement to the subaccount. // Note: Funding payment is the negative of settlement, i.e. positive settlement is equivalent diff --git a/protocol/x/clob/keeper/msg_server_cancel_orders.go b/protocol/x/clob/keeper/msg_server_cancel_orders.go index b5165d4bc1..02610fd93d 100644 --- a/protocol/x/clob/keeper/msg_server_cancel_orders.go +++ b/protocol/x/clob/keeper/msg_server_cancel_orders.go @@ -23,7 +23,7 @@ func (k msgServer) CancelOrder( ) (resp *types.MsgCancelOrderResponse, err error) { ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) - if err := k.Keeper.HandleMsgCancelOrder(ctx, msg); err != nil { + if err := k.Keeper.HandleMsgCancelOrder(ctx, msg, false); err != nil { return nil, err } @@ -37,6 +37,7 @@ func (k msgServer) CancelOrder( func (k Keeper) HandleMsgCancelOrder( ctx sdk.Context, msg *types.MsgCancelOrder, + isInternalOrder bool, ) (err error) { lib.AssertDeliverTxMode(ctx) @@ -110,17 +111,19 @@ func (k Keeper) HandleMsgCancelOrder( k.MustSetProcessProposerMatchesEvents(ctx, processProposerMatchesEvents) // 4. Add the relevant on-chain Indexer event for the cancellation. - k.GetIndexerEventManager().AddTxnEvent( - ctx, - indexerevents.SubtypeStatefulOrder, - indexerevents.StatefulOrderEventVersion, - indexer_manager.GetBytes( - indexerevents.NewStatefulOrderRemovalEvent( - msg.OrderId, - indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_USER_CANCELED, + if !isInternalOrder { // vault order indexer event logic is handled elsewhere + k.GetIndexerEventManager().AddTxnEvent( + ctx, + indexerevents.SubtypeStatefulOrder, + indexerevents.StatefulOrderEventVersion, + indexer_manager.GetBytes( + indexerevents.NewStatefulOrderRemovalEvent( + msg.OrderId, + indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_USER_CANCELED, + ), ), - ), - ) + ) + } return nil } diff --git a/protocol/x/clob/keeper/msg_server_place_order.go b/protocol/x/clob/keeper/msg_server_place_order.go index 5db309df39..d3801d5ad3 100644 --- a/protocol/x/clob/keeper/msg_server_place_order.go +++ b/protocol/x/clob/keeper/msg_server_place_order.go @@ -114,31 +114,35 @@ func (k Keeper) HandleMsgPlaceOrder( // 4. Emit the new order placement indexer event. if order.IsConditionalOrder() { - k.GetIndexerEventManager().AddTxnEvent( - ctx, - indexerevents.SubtypeStatefulOrder, - indexerevents.StatefulOrderEventVersion, - indexer_manager.GetBytes( - indexerevents.NewConditionalOrderPlacementEvent( - order, + if !isInternalOrder { // vault order indexer event logic is handled elsewhere + k.GetIndexerEventManager().AddTxnEvent( + ctx, + indexerevents.SubtypeStatefulOrder, + indexerevents.StatefulOrderEventVersion, + indexer_manager.GetBytes( + indexerevents.NewConditionalOrderPlacementEvent( + order, + ), ), - ), - ) + ) + } processProposerMatchesEvents.PlacedConditionalOrderIds = append( processProposerMatchesEvents.PlacedConditionalOrderIds, order.OrderId, ) } else { - k.GetIndexerEventManager().AddTxnEvent( - ctx, - indexerevents.SubtypeStatefulOrder, - indexerevents.StatefulOrderEventVersion, - indexer_manager.GetBytes( - indexerevents.NewLongTermOrderPlacementEvent( - order, + if !isInternalOrder { // vault order indexer event logic is handled elsewhere + k.GetIndexerEventManager().AddTxnEvent( + ctx, + indexerevents.SubtypeStatefulOrder, + indexerevents.StatefulOrderEventVersion, + indexer_manager.GetBytes( + indexerevents.NewLongTermOrderPlacementEvent( + order, + ), ), - ), - ) + ) + } processProposerMatchesEvents.PlacedLongTermOrderIds = append( processProposerMatchesEvents.PlacedLongTermOrderIds, order.OrderId, diff --git a/protocol/x/clob/keeper/process_single_match.go b/protocol/x/clob/keeper/process_single_match.go index fd132552a3..b936f9d481 100644 --- a/protocol/x/clob/keeper/process_single_match.go +++ b/protocol/x/clob/keeper/process_single_match.go @@ -51,16 +51,16 @@ func (k Keeper) ProcessSingleMatch( defer func() { if errors.Is(err, satypes.ErrFailedToUpdateSubaccounts) && !takerUpdateResult.IsSuccess() { takerSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, matchWithOrders.TakerOrder.GetSubaccountId()) - takerTnc, takerIMR, takerMMR, _ := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + riskTaker, _ := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: *takerSubaccount.Id}, ) log.ErrorLog(ctx, "collateralization check failed for liquidation", "takerSubaccount", fmt.Sprintf("%+v", takerSubaccount), - "takerTNC", takerTnc, - "takerIMR", takerIMR, - "takerMMR", takerMMR, + "takerTNC", riskTaker.NC, + "takerIMR", riskTaker.IMR, + "takerMMR", riskTaker.MMR, "liquidationOrder", fmt.Sprintf("%+v", matchWithOrders.TakerOrder), "makerOrder", fmt.Sprintf("%+v", matchWithOrders.MakerOrder), "fillAmount", matchWithOrders.FillAmount, diff --git a/protocol/x/clob/simulation/place_order.go b/protocol/x/clob/simulation/place_order.go index 8912a0b84d..1a08bde6fd 100644 --- a/protocol/x/clob/simulation/place_order.go +++ b/protocol/x/clob/simulation/place_order.go @@ -122,7 +122,7 @@ func SimulateMsgPlaceOrder( "Subaccount does not have enough free collateral to place minimum order", ), nil, nil } - if bigMinOrderQuoteQuantums.Cmp(lib.BigInt0()) == 0 { + if bigMinOrderQuoteQuantums.BitLen() == 0 { return simtypes.NoOpMsg( types.ModuleName, typeMsgPlaceOrder, @@ -263,7 +263,7 @@ func generateValidPlaceOrder( // Handle special order conditions. if reduceOnly { // Reduce only must be opposite of current positions in clob pair. - curPositionSign := currentPositionSizeQuantums.Cmp(lib.BigInt0()) + curPositionSign := currentPositionSizeQuantums.Sign() if curPositionSign < 0 { // currently short, order should go long orderSide = types.Order_SIDE_BUY @@ -302,7 +302,7 @@ func getMaxSubaccountOrderQuoteQuantums( sk types.SubaccountsKeeper, subaccountId satypes.SubaccountId, ) *big.Int { - bigNetCollateral, bigInitialMargin, _, err := sk.GetNetCollateralAndMarginRequirements( + risk, err := sk.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{ SubaccountId: subaccountId, @@ -312,6 +312,6 @@ func getMaxSubaccountOrderQuoteQuantums( panic(err) } - maxQuoteQuantums := new(big.Int).Sub(bigNetCollateral, bigInitialMargin) + maxQuoteQuantums := new(big.Int).Sub(risk.NC, risk.IMR) return lib.BigMin(maxQuoteQuantums, maxNonOverflowOrderQuoteQuantums) } diff --git a/protocol/x/clob/types/clob_keeper.go b/protocol/x/clob/types/clob_keeper.go index a7afa13bbf..da83594e6a 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -46,6 +46,7 @@ type ClobKeeper interface { HandleMsgCancelOrder( ctx sdk.Context, msg *MsgCancelOrder, + isInternalOrder bool, ) (err error) HandleMsgPlaceOrder( ctx sdk.Context, diff --git a/protocol/x/clob/types/expected_keepers.go b/protocol/x/clob/types/expected_keepers.go index 755aa4fd20..5b53c58c93 100644 --- a/protocol/x/clob/types/expected_keepers.go +++ b/protocol/x/clob/types/expected_keepers.go @@ -6,6 +6,7 @@ import ( "math/rand" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" perpetualsmoduletypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" @@ -27,9 +28,7 @@ type SubaccountsKeeper interface { ctx sdk.Context, update satypes.Update, ) ( - bigNetCollateral *big.Int, - bigInitialMargin *big.Int, - bigMaintenanceMargin *big.Int, + risk margin.Risk, err error, ) GetSubaccount( @@ -122,16 +121,6 @@ type PerpetualsKeeper interface { ctx sdk.Context, perpetualId uint32, ) (perpetualsmoduletypes.Perpetual, pricestypes.MarketPrice, error) - GetSettlementPpm( - ctx sdk.Context, - perpetualId uint32, - quantums *big.Int, - index *big.Int, - ) ( - bigNetSettlement *big.Int, - newFundingIndex *big.Int, - err error, - ) MaybeProcessNewFundingTickEpoch(ctx sdk.Context) GetInsuranceFundModuleAddress(ctx sdk.Context, perpetualId uint32) (sdk.AccAddress, error) } diff --git a/protocol/x/listing/genesis.go b/protocol/x/listing/genesis.go index 448b724d67..fcd391c6ab 100644 --- a/protocol/x/listing/genesis.go +++ b/protocol/x/listing/genesis.go @@ -8,9 +8,15 @@ import ( // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + k.InitializeForGenesis(ctx) + + if err := k.SetMarketsHardCap(ctx, genState.HardCapForMarkets); err != nil { + panic(err) + } } // ExportGenesis returns the module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - return &types.GenesisState{} + genesis := types.DefaultGenesis() + return genesis } diff --git a/protocol/x/listing/keeper/grpc_query_markets_hard_cap.go b/protocol/x/listing/keeper/grpc_query_markets_hard_cap.go new file mode 100644 index 0000000000..c6f2d76598 --- /dev/null +++ b/protocol/x/listing/keeper/grpc_query_markets_hard_cap.go @@ -0,0 +1,18 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" +) + +func (k Keeper) MarketsHardCap( + ctx context.Context, + req *types.QueryMarketsHardCap, +) (*types.QueryMarketsHardCapResponse, error) { + hardCap := k.GetMarketsHardCap(sdk.UnwrapSDKContext(ctx)) + return &types.QueryMarketsHardCapResponse{ + HardCap: hardCap, + }, nil +} diff --git a/protocol/x/listing/keeper/grpc_query_markets_hard_cap_test.go b/protocol/x/listing/keeper/grpc_query_markets_hard_cap_test.go new file mode 100644 index 0000000000..66ef0c1710 --- /dev/null +++ b/protocol/x/listing/keeper/grpc_query_markets_hard_cap_test.go @@ -0,0 +1,42 @@ +package keeper_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" + "github.com/stretchr/testify/require" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" +) + +func TestQueryMarketsHardCap(t *testing.T) { + tests := map[string]struct { + hardCap uint32 + }{ + "Hard cap: 0": { + hardCap: 0, + }, + "Hard cap: 100": { + hardCap: 100, + }, + } + + for name, tc := range tests { + t.Run( + name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.ListingKeeper + + // set hard cap for markets for test + err := k.SetMarketsHardCap(ctx, tc.hardCap) + require.NoError(t, err) + + // query hard cap for markets + resp, err := k.MarketsHardCap(ctx, &types.QueryMarketsHardCap{}) + require.NoError(t, err) + require.Equal(t, resp.HardCap, tc.hardCap) + }, + ) + } +} diff --git a/protocol/x/listing/keeper/keeper.go b/protocol/x/listing/keeper/keeper.go index 319158f723..032067c2ad 100644 --- a/protocol/x/listing/keeper/keeper.go +++ b/protocol/x/listing/keeper/keeper.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/lib" - "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" ) type ( diff --git a/protocol/x/listing/keeper/listing.go b/protocol/x/listing/keeper/listing.go new file mode 100644 index 0000000000..e228cd9398 --- /dev/null +++ b/protocol/x/listing/keeper/listing.go @@ -0,0 +1,25 @@ +package keeper + +import ( + gogotypes "github.com/cosmos/gogoproto/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" +) + +// Function to set hard cap on listed markets in module store +func (k Keeper) SetMarketsHardCap(ctx sdk.Context, hardCap uint32) error { + store := ctx.KVStore(k.storeKey) + value := gogotypes.UInt32Value{Value: hardCap} + store.Set([]byte(types.HardCapForMarketsKey), k.cdc.MustMarshal(&value)) + return nil +} + +// Function to get hard cap on listed markets from module store +func (k Keeper) GetMarketsHardCap(ctx sdk.Context) (hardCap uint32) { + store := ctx.KVStore(k.storeKey) + b := store.Get([]byte(types.HardCapForMarketsKey)) + var result gogotypes.UInt32Value + k.cdc.MustUnmarshal(b, &result) + return result.Value +} diff --git a/protocol/x/listing/keeper/msg_server_set_hard_cap.go b/protocol/x/listing/keeper/msg_server_set_hard_cap.go new file mode 100644 index 0000000000..965cf1e1ce --- /dev/null +++ b/protocol/x/listing/keeper/msg_server_set_hard_cap.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" +) + +func (k msgServer) SetMarketsHardCap( + goCtx context.Context, + msg *types.MsgSetMarketsHardCap, +) (*types.MsgSetMarketsHardCapResponse, error) { + ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) + + // Check if the sender has the authority to set the hard cap + if !k.HasAuthority(msg.Authority) { + return nil, errorsmod.Wrapf( + govtypes.ErrInvalidSigner, + "invalid authority %s", + msg.Authority, + ) + } + + // Set the hard cap for listed markets + err := k.Keeper.SetMarketsHardCap(ctx, msg.HardCapForMarkets) + if err != nil { + return nil, err + } + + return &types.MsgSetMarketsHardCapResponse{}, nil +} diff --git a/protocol/x/listing/keeper/msg_server_set_hard_cap_test.go b/protocol/x/listing/keeper/msg_server_set_hard_cap_test.go new file mode 100644 index 0000000000..bfc473ec88 --- /dev/null +++ b/protocol/x/listing/keeper/msg_server_set_hard_cap_test.go @@ -0,0 +1,64 @@ +package keeper_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/lib" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" + "github.com/stretchr/testify/require" +) + +func TestMsgSetMarketsHardCap(t *testing.T) { + tests := map[string]struct { + // Msg. + msg *types.MsgSetMarketsHardCap + // Expected error + expectedErr string + }{ + "Success - Hard cap to 100": { + msg: &types.MsgSetMarketsHardCap{ + Authority: lib.GovModuleAddress.String(), + HardCapForMarkets: 100, + }, + }, + "Success - Disabled": { + msg: &types.MsgSetMarketsHardCap{ + Authority: lib.GovModuleAddress.String(), + HardCapForMarkets: 0, + }, + }, + "Failure - Invalid Authority": { + msg: &types.MsgSetMarketsHardCap{ + Authority: constants.AliceAccAddress.String(), + HardCapForMarkets: 100, + }, + expectedErr: "invalid authority", + }, + "Failure - Empty authority": { + msg: &types.MsgSetMarketsHardCap{ + HardCapForMarkets: 100, + }, + expectedErr: "invalid authority", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.ListingKeeper + ms := keeper.NewMsgServerImpl(k) + _, err := ms.SetMarketsHardCap(ctx, tc.msg) + if tc.expectedErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + } else { + enabledFlag := k.GetMarketsHardCap(ctx) + require.Equal(t, tc.msg.HardCapForMarkets, enabledFlag) + } + }) + } +} diff --git a/protocol/x/listing/keeper/msg_server_test.go b/protocol/x/listing/keeper/msg_server_test.go index a49577ab61..bccbd4a199 100644 --- a/protocol/x/listing/keeper/msg_server_test.go +++ b/protocol/x/listing/keeper/msg_server_test.go @@ -5,15 +5,15 @@ import ( "testing" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" - "github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper" - "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" "github.com/stretchr/testify/require" ) func setupMsgServer(t *testing.T) (keeper.Keeper, types.MsgServer, context.Context) { tApp := testapp.NewTestAppBuilder(t).Build() ctx := tApp.InitChain() - k := tApp.App.VaultKeeper + k := tApp.App.ListingKeeper return k, keeper.NewMsgServerImpl(k), ctx } diff --git a/protocol/x/listing/module.go b/protocol/x/listing/module.go index 99d2d44446..79d2a6c3a4 100644 --- a/protocol/x/listing/module.go +++ b/protocol/x/listing/module.go @@ -75,7 +75,8 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod } // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. -func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { +} // GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to // generate new transactions containing messages defined in the module. diff --git a/protocol/x/listing/types/genesis.go b/protocol/x/listing/types/genesis.go index 09583a5f2e..0bbef0cd95 100644 --- a/protocol/x/listing/types/genesis.go +++ b/protocol/x/listing/types/genesis.go @@ -2,7 +2,9 @@ package types // DefaultGenesis returns the default stats genesis state. func DefaultGenesis() *GenesisState { - return &GenesisState{} + return &GenesisState{ + HardCapForMarkets: 0, + } } // Validate performs basic genesis state validation returning an error upon any diff --git a/protocol/x/listing/types/genesis.pb.go b/protocol/x/listing/types/genesis.pb.go index 878c1c27c2..f42e4ac001 100644 --- a/protocol/x/listing/types/genesis.pb.go +++ b/protocol/x/listing/types/genesis.pb.go @@ -24,6 +24,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines `x/listing`'s genesis state. type GenesisState struct { + // hard_cap_for_markets is the hard cap for the number of markets that can be + // listed + HardCapForMarkets uint32 `protobuf:"varint,1,opt,name=hard_cap_for_markets,json=hardCapForMarkets,proto3" json:"hard_cap_for_markets,omitempty"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -59,6 +62,13 @@ func (m *GenesisState) XXX_DiscardUnknown() { var xxx_messageInfo_GenesisState proto.InternalMessageInfo +func (m *GenesisState) GetHardCapForMarkets() uint32 { + if m != nil { + return m.HardCapForMarkets + } + return 0 +} + func init() { proto.RegisterType((*GenesisState)(nil), "dydxprotocol.listing.GenesisState") } @@ -68,16 +78,19 @@ func init() { } var fileDescriptor_d378c53d0f5a34b3 = []byte{ - // 143 bytes of a gzipped FileDescriptorProto + // 186 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0xa9, 0x4c, 0xa9, 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0xcf, 0xc9, 0x2c, 0x2e, 0xc9, 0xcc, 0x4b, 0xd7, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x03, 0x4b, 0x08, 0x89, 0x20, 0xab, 0xd1, - 0x83, 0xaa, 0x51, 0xe2, 0xe3, 0xe2, 0x71, 0x87, 0x28, 0x0b, 0x2e, 0x49, 0x2c, 0x49, 0x75, 0x0a, - 0x3e, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, - 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xcb, 0xf4, 0xcc, 0x92, 0x8c, - 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x14, 0xeb, 0xca, 0x4c, 0x74, 0x93, 0x33, 0x12, 0x33, - 0xf3, 0xf4, 0xe1, 0x22, 0x15, 0x70, 0x27, 0x94, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x65, - 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x20, 0xa7, 0x52, 0xc2, 0xa7, 0x00, 0x00, 0x00, + 0x83, 0xaa, 0x51, 0xb2, 0xe7, 0xe2, 0x71, 0x87, 0x28, 0x0b, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0xd2, + 0xe7, 0x12, 0xc9, 0x48, 0x2c, 0x4a, 0x89, 0x4f, 0x4e, 0x2c, 0x88, 0x4f, 0xcb, 0x2f, 0x8a, 0xcf, + 0x4d, 0x2c, 0xca, 0x4e, 0x2d, 0x29, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0x12, 0x04, 0xc9, + 0x39, 0x27, 0x16, 0xb8, 0xe5, 0x17, 0xf9, 0x42, 0x24, 0x9c, 0x82, 0x4f, 0x3c, 0x92, 0x63, 0xbc, + 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, + 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x32, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, + 0x57, 0x1f, 0xc5, 0x7d, 0x65, 0x26, 0xba, 0xc9, 0x19, 0x89, 0x99, 0x79, 0xfa, 0x70, 0x91, 0x0a, + 0xb8, 0x9b, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x32, 0xc6, 0x80, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x2c, 0x8a, 0x33, 0x2d, 0xd8, 0x00, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -100,6 +113,11 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.HardCapForMarkets != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.HardCapForMarkets)) + i-- + dAtA[i] = 0x8 + } return len(dAtA) - i, nil } @@ -120,6 +138,9 @@ func (m *GenesisState) Size() (n int) { } var l int _ = l + if m.HardCapForMarkets != 0 { + n += 1 + sovGenesis(uint64(m.HardCapForMarkets)) + } return n } @@ -158,6 +179,25 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HardCapForMarkets", wireType) + } + m.HardCapForMarkets = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.HardCapForMarkets |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/protocol/x/listing/types/keys.go b/protocol/x/listing/types/keys.go index 983efc0f9c..19e480890d 100644 --- a/protocol/x/listing/types/keys.go +++ b/protocol/x/listing/types/keys.go @@ -8,3 +8,9 @@ const ( // StoreKey defines the primary module store key. StoreKey = ModuleName ) + +// State. +const ( + // HardCapForMarketsKey is the key to retrieve the hard cap for listed markets. + HardCapForMarketsKey = "HardCapForMarkets" +) diff --git a/protocol/x/listing/types/query.pb.go b/protocol/x/listing/types/query.pb.go index 1042de3679..3fbff0c909 100644 --- a/protocol/x/listing/types/query.pb.go +++ b/protocol/x/listing/types/query.pb.go @@ -9,7 +9,11 @@ import ( grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" math "math" + math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. @@ -23,19 +27,111 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// Queries for the hard cap on listed markets +type QueryMarketsHardCap struct { +} + +func (m *QueryMarketsHardCap) Reset() { *m = QueryMarketsHardCap{} } +func (m *QueryMarketsHardCap) String() string { return proto.CompactTextString(m) } +func (*QueryMarketsHardCap) ProtoMessage() {} +func (*QueryMarketsHardCap) Descriptor() ([]byte, []int) { + return fileDescriptor_6c3602ac8eedf7cc, []int{0} +} +func (m *QueryMarketsHardCap) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMarketsHardCap) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMarketsHardCap.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMarketsHardCap) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMarketsHardCap.Merge(m, src) +} +func (m *QueryMarketsHardCap) XXX_Size() int { + return m.Size() +} +func (m *QueryMarketsHardCap) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMarketsHardCap.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMarketsHardCap proto.InternalMessageInfo + +// Response type indicating the hard cap on listed markets +type QueryMarketsHardCapResponse struct { + HardCap uint32 `protobuf:"varint,1,opt,name=hard_cap,json=hardCap,proto3" json:"hard_cap,omitempty"` +} + +func (m *QueryMarketsHardCapResponse) Reset() { *m = QueryMarketsHardCapResponse{} } +func (m *QueryMarketsHardCapResponse) String() string { return proto.CompactTextString(m) } +func (*QueryMarketsHardCapResponse) ProtoMessage() {} +func (*QueryMarketsHardCapResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6c3602ac8eedf7cc, []int{1} +} +func (m *QueryMarketsHardCapResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMarketsHardCapResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMarketsHardCapResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMarketsHardCapResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMarketsHardCapResponse.Merge(m, src) +} +func (m *QueryMarketsHardCapResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryMarketsHardCapResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMarketsHardCapResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMarketsHardCapResponse proto.InternalMessageInfo + +func (m *QueryMarketsHardCapResponse) GetHardCap() uint32 { + if m != nil { + return m.HardCap + } + return 0 +} + +func init() { + proto.RegisterType((*QueryMarketsHardCap)(nil), "dydxprotocol.listing.QueryMarketsHardCap") + proto.RegisterType((*QueryMarketsHardCapResponse)(nil), "dydxprotocol.listing.QueryMarketsHardCapResponse") +} + func init() { proto.RegisterFile("dydxprotocol/listing/query.proto", fileDescriptor_6c3602ac8eedf7cc) } var fileDescriptor_6c3602ac8eedf7cc = []byte{ - // 136 bytes of a gzipped FileDescriptorProto + // 215 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0xa9, 0x4c, 0xa9, 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0xcf, 0xc9, 0x2c, 0x2e, 0xc9, 0xcc, 0x4b, 0xd7, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x03, 0x0b, 0x0b, 0x89, 0x20, 0xab, 0xd0, 0x83, 0xaa, - 0x30, 0x62, 0xe7, 0x62, 0x0d, 0x04, 0x29, 0x72, 0x0a, 0x3e, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, - 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, - 0x63, 0x39, 0x86, 0x28, 0xcb, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, - 0x14, 0x5b, 0xca, 0x4c, 0x74, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0xe1, 0x22, 0x15, 0x70, 0x9b, - 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x32, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xd9, 0xb1, 0x14, 0x70, 0x9e, 0x00, 0x00, 0x00, + 0x50, 0x12, 0xe5, 0x12, 0x0e, 0x04, 0x29, 0xf2, 0x4d, 0x2c, 0xca, 0x4e, 0x2d, 0x29, 0xf6, 0x48, + 0x2c, 0x4a, 0x71, 0x4e, 0x2c, 0x50, 0xb2, 0xe0, 0x92, 0xc6, 0x22, 0x1c, 0x94, 0x5a, 0x5c, 0x90, + 0x9f, 0x57, 0x9c, 0x2a, 0x24, 0xc9, 0xc5, 0x91, 0x91, 0x58, 0x94, 0x12, 0x9f, 0x9c, 0x58, 0x20, + 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x1b, 0xc4, 0x9e, 0x01, 0x51, 0x62, 0x54, 0xce, 0xc5, 0x0a, 0xd6, + 0x29, 0x94, 0xc7, 0xc5, 0x87, 0xaa, 0x5b, 0x48, 0x53, 0x0f, 0x9b, 0x13, 0xf4, 0xb0, 0x58, 0x24, + 0x65, 0x48, 0xb4, 0x52, 0x98, 0x9b, 0x9c, 0x82, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, + 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, + 0x8e, 0x21, 0xca, 0x32, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x25, + 0x98, 0xca, 0x4c, 0x74, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0xe1, 0x22, 0x15, 0xf0, 0xa0, 0x2b, + 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03, 0xcb, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x75, + 0xde, 0xd9, 0xa5, 0x5f, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -50,6 +146,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type QueryClient interface { + // Queries for the hard cap number of listed markets + MarketsHardCap(ctx context.Context, in *QueryMarketsHardCap, opts ...grpc.CallOption) (*QueryMarketsHardCapResponse, error) } type queryClient struct { @@ -60,22 +158,353 @@ func NewQueryClient(cc grpc1.ClientConn) QueryClient { return &queryClient{cc} } +func (c *queryClient) MarketsHardCap(ctx context.Context, in *QueryMarketsHardCap, opts ...grpc.CallOption) (*QueryMarketsHardCapResponse, error) { + out := new(QueryMarketsHardCapResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.listing.Query/MarketsHardCap", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { + // Queries for the hard cap number of listed markets + MarketsHardCap(context.Context, *QueryMarketsHardCap) (*QueryMarketsHardCapResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. type UnimplementedQueryServer struct { } +func (*UnimplementedQueryServer) MarketsHardCap(ctx context.Context, req *QueryMarketsHardCap) (*QueryMarketsHardCapResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MarketsHardCap not implemented") +} + func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) } +func _Query_MarketsHardCap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryMarketsHardCap) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).MarketsHardCap(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.listing.Query/MarketsHardCap", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).MarketsHardCap(ctx, req.(*QueryMarketsHardCap)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "dydxprotocol.listing.Query", HandlerType: (*QueryServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "dydxprotocol/listing/query.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "MarketsHardCap", + Handler: _Query_MarketsHardCap_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "dydxprotocol/listing/query.proto", +} + +func (m *QueryMarketsHardCap) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMarketsHardCap) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMarketsHardCap) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryMarketsHardCapResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMarketsHardCapResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMarketsHardCapResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.HardCap != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.HardCap)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryMarketsHardCap) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryMarketsHardCapResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.HardCap != 0 { + n += 1 + sovQuery(uint64(m.HardCap)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *QueryMarketsHardCap) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMarketsHardCap: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMarketsHardCap: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMarketsHardCapResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMarketsHardCapResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMarketsHardCapResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HardCap", wireType) + } + m.HardCap = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.HardCap |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/listing/types/tx.pb.go b/protocol/x/listing/types/tx.pb.go index 60c163c2b6..faf1455764 100644 --- a/protocol/x/listing/types/tx.pb.go +++ b/protocol/x/listing/types/tx.pb.go @@ -6,10 +6,16 @@ package types import ( context "context" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" math "math" + math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. @@ -23,19 +29,127 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// MsgSetMarketsHardCap is used to set a hard cap on the number of markets +// listed +type MsgSetMarketsHardCap struct { + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // Hard cap for the total number of markets listed + HardCapForMarkets uint32 `protobuf:"varint,2,opt,name=hard_cap_for_markets,json=hardCapForMarkets,proto3" json:"hard_cap_for_markets,omitempty"` +} + +func (m *MsgSetMarketsHardCap) Reset() { *m = MsgSetMarketsHardCap{} } +func (m *MsgSetMarketsHardCap) String() string { return proto.CompactTextString(m) } +func (*MsgSetMarketsHardCap) ProtoMessage() {} +func (*MsgSetMarketsHardCap) Descriptor() ([]byte, []int) { + return fileDescriptor_144a579c1e2dcb94, []int{0} +} +func (m *MsgSetMarketsHardCap) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetMarketsHardCap) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetMarketsHardCap.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetMarketsHardCap) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetMarketsHardCap.Merge(m, src) +} +func (m *MsgSetMarketsHardCap) XXX_Size() int { + return m.Size() +} +func (m *MsgSetMarketsHardCap) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetMarketsHardCap.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetMarketsHardCap proto.InternalMessageInfo + +func (m *MsgSetMarketsHardCap) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgSetMarketsHardCap) GetHardCapForMarkets() uint32 { + if m != nil { + return m.HardCapForMarkets + } + return 0 +} + +// MsgSetMarketsHardCapResponse defines the MsgSetMarketsHardCap response +type MsgSetMarketsHardCapResponse struct { +} + +func (m *MsgSetMarketsHardCapResponse) Reset() { *m = MsgSetMarketsHardCapResponse{} } +func (m *MsgSetMarketsHardCapResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSetMarketsHardCapResponse) ProtoMessage() {} +func (*MsgSetMarketsHardCapResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_144a579c1e2dcb94, []int{1} +} +func (m *MsgSetMarketsHardCapResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetMarketsHardCapResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetMarketsHardCapResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetMarketsHardCapResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetMarketsHardCapResponse.Merge(m, src) +} +func (m *MsgSetMarketsHardCapResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSetMarketsHardCapResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetMarketsHardCapResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetMarketsHardCapResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgSetMarketsHardCap)(nil), "dydxprotocol.listing.MsgSetMarketsHardCap") + proto.RegisterType((*MsgSetMarketsHardCapResponse)(nil), "dydxprotocol.listing.MsgSetMarketsHardCapResponse") +} + func init() { proto.RegisterFile("dydxprotocol/listing/tx.proto", fileDescriptor_144a579c1e2dcb94) } var fileDescriptor_144a579c1e2dcb94 = []byte{ - // 131 bytes of a gzipped FileDescriptorProto + // 310 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0xa9, 0x4c, 0xa9, 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0xcf, 0xc9, 0x2c, 0x2e, 0xc9, 0xcc, 0x4b, - 0xd7, 0x2f, 0xa9, 0xd0, 0x03, 0x8b, 0x09, 0x89, 0x20, 0x4b, 0xeb, 0x41, 0xa5, 0x8d, 0x58, 0xb9, - 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0x82, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, - 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, - 0xca, 0x32, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xc5, 0x82, 0x32, - 0x13, 0xdd, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x7d, 0xb8, 0x48, 0x05, 0xc2, 0xd2, 0xca, 0x82, 0xd4, - 0xe2, 0x24, 0x36, 0xb0, 0x8c, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x99, 0x01, 0x33, 0x7d, 0x99, - 0x00, 0x00, 0x00, + 0xd7, 0x2f, 0xa9, 0xd0, 0x03, 0x8b, 0x09, 0x89, 0x20, 0x4b, 0xeb, 0x41, 0xa5, 0xa5, 0x24, 0x93, + 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xe3, 0xc1, 0x12, 0xfa, 0x10, 0x0e, 0x44, 0x83, 0x94, 0x38, 0x84, + 0xa7, 0x9f, 0x5b, 0x9c, 0xae, 0x5f, 0x66, 0x08, 0xa2, 0x20, 0x12, 0x4a, 0xfd, 0x8c, 0x5c, 0x22, + 0xbe, 0xc5, 0xe9, 0xc1, 0xa9, 0x25, 0xbe, 0x89, 0x45, 0xd9, 0xa9, 0x25, 0xc5, 0x1e, 0x89, 0x45, + 0x29, 0xce, 0x89, 0x05, 0x42, 0x66, 0x5c, 0x9c, 0x89, 0xa5, 0x25, 0x19, 0xf9, 0x45, 0x99, 0x25, + 0x95, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x4e, 0x12, 0x97, 0xb6, 0xe8, 0x8a, 0x40, 0x8d, 0x75, + 0x4c, 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x0e, 0x2e, 0x29, 0xca, 0xcc, 0x4b, 0x0f, 0x42, 0x28, 0x15, + 0xd2, 0xe7, 0x12, 0xc9, 0x48, 0x2c, 0x4a, 0x89, 0x4f, 0x4e, 0x2c, 0x88, 0x4f, 0xcb, 0x2f, 0x8a, + 0xcf, 0x85, 0x18, 0x2b, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x1b, 0x24, 0x98, 0x01, 0x31, 0xde, 0x2d, + 0xbf, 0x08, 0x6a, 0x9f, 0x15, 0x5f, 0xd3, 0xf3, 0x0d, 0x5a, 0x08, 0x03, 0x94, 0xe4, 0xb8, 0x64, + 0xb0, 0x39, 0x28, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0xd5, 0xa8, 0x8a, 0x8b, 0xd9, 0xb7, + 0x38, 0x5d, 0xa8, 0x98, 0x4b, 0x10, 0xd3, 0xd1, 0x5a, 0x7a, 0xd8, 0x02, 0x46, 0x0f, 0x9b, 0x79, + 0x52, 0x46, 0xc4, 0xab, 0x85, 0xd9, 0xed, 0x14, 0x7c, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, + 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, + 0x72, 0x0c, 0x51, 0x96, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x28, + 0x71, 0x57, 0x66, 0xa2, 0x9b, 0x9c, 0x91, 0x98, 0x99, 0xa7, 0x0f, 0x17, 0xa9, 0x40, 0xc4, 0x67, + 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x58, 0xc6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xea, 0x0c, + 0xfe, 0xfa, 0xf4, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -50,6 +164,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { + // SetMarketsHardCap sets a hard cap on the number of markets listed + SetMarketsHardCap(ctx context.Context, in *MsgSetMarketsHardCap, opts ...grpc.CallOption) (*MsgSetMarketsHardCapResponse, error) } type msgClient struct { @@ -60,22 +176,396 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } +func (c *msgClient) SetMarketsHardCap(ctx context.Context, in *MsgSetMarketsHardCap, opts ...grpc.CallOption) (*MsgSetMarketsHardCapResponse, error) { + out := new(MsgSetMarketsHardCapResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.listing.Msg/SetMarketsHardCap", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { + // SetMarketsHardCap sets a hard cap on the number of markets listed + SetMarketsHardCap(context.Context, *MsgSetMarketsHardCap) (*MsgSetMarketsHardCapResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. type UnimplementedMsgServer struct { } +func (*UnimplementedMsgServer) SetMarketsHardCap(ctx context.Context, req *MsgSetMarketsHardCap) (*MsgSetMarketsHardCapResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetMarketsHardCap not implemented") +} + func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } +func _Msg_SetMarketsHardCap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSetMarketsHardCap) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SetMarketsHardCap(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.listing.Msg/SetMarketsHardCap", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SetMarketsHardCap(ctx, req.(*MsgSetMarketsHardCap)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "dydxprotocol.listing.Msg", HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "dydxprotocol/listing/tx.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "SetMarketsHardCap", + Handler: _Msg_SetMarketsHardCap_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "dydxprotocol/listing/tx.proto", +} + +func (m *MsgSetMarketsHardCap) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetMarketsHardCap) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetMarketsHardCap) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.HardCapForMarkets != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.HardCapForMarkets)) + i-- + dAtA[i] = 0x10 + } + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } + +func (m *MsgSetMarketsHardCapResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetMarketsHardCapResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetMarketsHardCapResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgSetMarketsHardCap) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.HardCapForMarkets != 0 { + n += 1 + sovTx(uint64(m.HardCapForMarkets)) + } + return n +} + +func (m *MsgSetMarketsHardCapResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgSetMarketsHardCap) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetMarketsHardCap: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetMarketsHardCap: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HardCapForMarkets", wireType) + } + m.HardCapForMarkets = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.HardCapForMarkets |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSetMarketsHardCapResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetMarketsHardCapResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetMarketsHardCapResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index e15d7aa380..64f09711c3 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -874,40 +874,6 @@ func (k Keeper) GetNetCollateral( return k.GetNetNotional(ctx, id, bigQuantums) } -// GetSettlementPpm returns the net settlement amount ppm (in quote quantums) given -// the perpetual Id and position size (in base quantums). -// When handling rounding, always round positive settlement amount to zero, and -// negative amount to negative infinity. This ensures total amount of value does -// not increase after settlement. -// Example: -// For a round of funding payments, accounts A, B are to receive 102.5 quote quantums; -// account C is to pay 205 quote quantums. -// After settlement, accounts A, B are credited 102 quote quantum each; account C -// is debited 205 quote quantums. -func (k Keeper) GetSettlementPpm( - ctx sdk.Context, - perpetualId uint32, - quantums *big.Int, - index *big.Int, -) ( - bigNetSettlementPpm *big.Int, - newFundingIndex *big.Int, - err error, -) { - // Get the perpetual for newest FundingIndex. - perpetual, err := k.GetPerpetual(ctx, perpetualId) - if err != nil { - return big.NewInt(0), big.NewInt(0), err - } - - bigNetSettlementPpm, newFundingIndex = perplib.GetSettlementPpmWithPerpetual( - perpetual, - quantums, - index, - ) - return bigNetSettlementPpm, newFundingIndex, nil -} - // GetPremiumSamples reads premium samples from the current `funding-tick` epoch, // stored in a `PremiumStore` struct. func (k Keeper) GetPremiumSamples(ctx sdk.Context) ( diff --git a/protocol/x/perpetuals/keeper/perpetual_test.go b/protocol/x/perpetuals/keeper/perpetual_test.go index a6218408dc..856d629dbb 100644 --- a/protocol/x/perpetuals/keeper/perpetual_test.go +++ b/protocol/x/perpetuals/keeper/perpetual_test.go @@ -1210,114 +1210,6 @@ func TestGetNetCollateral_MarketNotFound(t *testing.T) { require.ErrorIs(t, err, types.ErrMarketDoesNotExist) } -func TestGetSettlementPpm_Success(t *testing.T) { - tests := map[string]struct { - quantums *big.Int - perpetualFundingIndex *big.Int - prevIndex *big.Int - expectedSettlementPpm *big.Int - }{ - "is long, index went from negative to positive": { - quantums: big.NewInt(30_000), - prevIndex: big.NewInt(-100), - perpetualFundingIndex: big.NewInt(100), - expectedSettlementPpm: big.NewInt(-6_000_000), - }, - "is long, index unchanged": { - quantums: big.NewInt(1_000_000), - prevIndex: big.NewInt(100), - perpetualFundingIndex: big.NewInt(100), - expectedSettlementPpm: big.NewInt(0), - }, - "is long, index went from positive to zero": { - quantums: big.NewInt(10_000_000), - prevIndex: big.NewInt(100), - perpetualFundingIndex: big.NewInt(0), - expectedSettlementPpm: big.NewInt(1_000_000_000), - }, - "is long, index went from positive to negative": { - quantums: big.NewInt(10_000_000), - prevIndex: big.NewInt(100), - perpetualFundingIndex: big.NewInt(-200), - expectedSettlementPpm: big.NewInt(3_000_000_000), - }, - "is short, index went from negative to positive": { - quantums: big.NewInt(-30_000), - prevIndex: big.NewInt(-100), - perpetualFundingIndex: big.NewInt(100), - expectedSettlementPpm: big.NewInt(6_000_000), - }, - "is short, index unchanged": { - quantums: big.NewInt(-1_000), - prevIndex: big.NewInt(100), - perpetualFundingIndex: big.NewInt(100), - expectedSettlementPpm: big.NewInt(0), - }, - "is short, index went from positive to zero": { - quantums: big.NewInt(-5_000_000), - prevIndex: big.NewInt(100), - perpetualFundingIndex: big.NewInt(0), - expectedSettlementPpm: big.NewInt(-500_000_000), - }, - "is short, index went from positive to negative": { - quantums: big.NewInt(-5_000_000), - prevIndex: big.NewInt(100), - perpetualFundingIndex: big.NewInt(-50), - expectedSettlementPpm: big.NewInt(-750_000_000), - }, - } - - // Run tests. - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Test suite setup. - pc := keepertest.PerpetualsKeepers(t) - // Create liquidity tiers. - keepertest.CreateTestLiquidityTiers(t, pc.Ctx, pc.PerpetualsKeeper) - perps, err := keepertest.CreateNPerpetuals(t, pc.Ctx, pc.PerpetualsKeeper, pc.PricesKeeper, 1) - require.NoError(t, err) - - perpetualId := perps[0].Params.Id - - // Since FundingIndex starts at zero, tc.perpetualFundingIndex will be - // the current FundingIndex. - err = pc.PerpetualsKeeper.ModifyFundingIndex(pc.Ctx, perpetualId, tc.perpetualFundingIndex) - require.NoError(t, err) - - bigNetSettlementPpm, newFundingIndex, err := pc.PerpetualsKeeper.GetSettlementPpm( - pc.Ctx, - perpetualId, - tc.quantums, - tc.prevIndex, - ) - require.NoError(t, err) - - require.Equal(t, - tc.perpetualFundingIndex, - newFundingIndex, - ) - - require.Equal(t, - 0, - tc.expectedSettlementPpm.Cmp(bigNetSettlementPpm), - ) - }) - } -} - -func TestGetSettlementPpm_PerpetualNotFound(t *testing.T) { - pc := keepertest.PerpetualsKeepers(t) - nonExistentPerpetualId := uint32(0) - _, _, err := pc.PerpetualsKeeper.GetSettlementPpm( - pc.Ctx, - nonExistentPerpetualId, // perpetualId - big.NewInt(-100), // quantum - big.NewInt(0), // index - ) - require.EqualError(t, err, errorsmod.Wrap(types.ErrPerpetualDoesNotExist, fmt.Sprint(nonExistentPerpetualId)).Error()) - require.ErrorIs(t, err, types.ErrPerpetualDoesNotExist) -} - func TestModifyFundingIndex_Success(t *testing.T) { pc := keepertest.PerpetualsKeepers(t) // Create liquidity tiers and perpetuals, diff --git a/protocol/x/perpetuals/lib/lib.go b/protocol/x/perpetuals/lib/lib.go index 261ea1469e..394429e7fa 100644 --- a/protocol/x/perpetuals/lib/lib.go +++ b/protocol/x/perpetuals/lib/lib.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ) @@ -18,21 +19,21 @@ func GetSettlementPpmWithPerpetual( bigNetSettlementPpm *big.Int, newFundingIndex *big.Int, ) { - indexDelta := new(big.Int).Sub(perpetual.FundingIndex.BigInt(), index) + fundingIndex := perpetual.FundingIndex.BigInt() - // if indexDelta is zero, then net settlement is zero. - if indexDelta.Sign() == 0 { - return big.NewInt(0), perpetual.FundingIndex.BigInt() + // If no change in funding, return 0. + if fundingIndex.Cmp(index) == 0 { + return big.NewInt(0), fundingIndex } - bigNetSettlementPpm = new(big.Int).Mul(indexDelta, quantums) + // The settlement is a signed value. + // If the index delta is positive and the quantums is positive (long), then settlement is negative. + // Thus, always negate the value of the multiplication of the index delta and the quantums. + result := new(big.Int).Sub(fundingIndex, index) + result = result.Mul(result, quantums) + result = result.Neg(result) - // `bigNetSettlementPpm` carries sign. `indexDelta` is the increase in `fundingIndex`, so if - // the position is long (positive), the net settlement should be short (negative), and vice versa. - // Thus, always negate `bigNetSettlementPpm` here. - bigNetSettlementPpm = bigNetSettlementPpm.Neg(bigNetSettlementPpm) - - return bigNetSettlementPpm, perpetual.FundingIndex.BigInt() + return result, fundingIndex } // GetNetCollateralAndMarginRequirements returns the net collateral, initial margin requirement, @@ -43,22 +44,24 @@ func GetNetCollateralAndMarginRequirements( liquidityTier types.LiquidityTier, quantums *big.Int, ) ( - nc *big.Int, - imr *big.Int, - mmr *big.Int, + risk margin.Risk, ) { - nc = GetNetNotionalInQuoteQuantums( + nc := GetNetNotionalInQuoteQuantums( perpetual, marketPrice, quantums, ) - imr, mmr = GetMarginRequirementsInQuoteQuantums( + imr, mmr := GetMarginRequirementsInQuoteQuantums( perpetual, marketPrice, liquidityTier, quantums, ) - return nc, imr, mmr + return margin.Risk{ + NC: nc, + IMR: imr, + MMR: mmr, + } } // GetNetNotionalInQuoteQuantums returns the net notional in quote quantums, which can be diff --git a/protocol/x/perpetuals/lib/lib_test.go b/protocol/x/perpetuals/lib/lib_test.go index 52721a4a8c..b366e3a247 100644 --- a/protocol/x/perpetuals/lib/lib_test.go +++ b/protocol/x/perpetuals/lib/lib_test.go @@ -36,7 +36,6 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { quantums *big.Int index *big.Int expectedNetSettlementPpm *big.Int - expectedNewFundingIndex *big.Int }{ "zero indexDelta": { perpetual: types.Perpetual{ @@ -45,7 +44,6 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { quantums: big.NewInt(1_000_000), index: big.NewInt(4_000_000_000), expectedNetSettlementPpm: big.NewInt(0), - expectedNewFundingIndex: big.NewInt(4_000_000_000), }, "positive indexDelta, positive quantums": { perpetual: types.Perpetual{ @@ -54,7 +52,6 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { quantums: big.NewInt(1_000_000), index: big.NewInt(100_000_000_000), expectedNetSettlementPpm: big.NewInt(-23_456_789_123_000_000), - expectedNewFundingIndex: big.NewInt(123_456_789_123), }, "positive indexDelta, negative quantums": { perpetual: types.Perpetual{ @@ -63,7 +60,6 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { quantums: big.NewInt(-1_000_000), index: big.NewInt(100_000_000_000), expectedNetSettlementPpm: big.NewInt(23_456_789_123_000_000), - expectedNewFundingIndex: big.NewInt(123_456_789_123), }, "negative indexDelta, positive quantums": { perpetual: types.Perpetual{ @@ -72,7 +68,6 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { quantums: big.NewInt(1_000_000), index: big.NewInt(-100_000_000_000), expectedNetSettlementPpm: big.NewInt(23_456_789_123_000_000), - expectedNewFundingIndex: big.NewInt(-123_456_789_123), }, "negative indexDelta, negative quantums": { perpetual: types.Perpetual{ @@ -81,7 +76,54 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { quantums: big.NewInt(-1_000_000), index: big.NewInt(-100_000_000_000), expectedNetSettlementPpm: big.NewInt(-23_456_789_123_000_000), - expectedNewFundingIndex: big.NewInt(-123_456_789_123), + }, + "is long, index went from negative to positive": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(100)}, + quantums: big.NewInt(30_000), + index: big.NewInt(-100), + expectedNetSettlementPpm: big.NewInt(-6_000_000), + }, + "is long, index unchanged": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(100)}, + quantums: big.NewInt(1_000_000), + index: big.NewInt(100), + expectedNetSettlementPpm: big.NewInt(0), + }, + "is long, index went from positive to zero": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(0)}, + quantums: big.NewInt(10_000_000), + index: big.NewInt(100), + expectedNetSettlementPpm: big.NewInt(1_000_000_000), + }, + "is long, index went from positive to negative": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(-200)}, + quantums: big.NewInt(10_000_000), + index: big.NewInt(100), + expectedNetSettlementPpm: big.NewInt(3_000_000_000), + }, + "is short, index went from negative to positive": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(100)}, + quantums: big.NewInt(-30_000), + index: big.NewInt(-100), + expectedNetSettlementPpm: big.NewInt(6_000_000), + }, + "is short, index unchanged": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(100)}, + quantums: big.NewInt(-1_000), + index: big.NewInt(100), + expectedNetSettlementPpm: big.NewInt(0), + }, + "is short, index went from positive to zero": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(0)}, + quantums: big.NewInt(-5_000_000), + index: big.NewInt(100), + expectedNetSettlementPpm: big.NewInt(-500_000_000), + }, + "is short, index went from positive to negative": { + perpetual: types.Perpetual{FundingIndex: dtypes.NewInt(-50)}, + quantums: big.NewInt(-5_000_000), + index: big.NewInt(100), + expectedNetSettlementPpm: big.NewInt(-750_000_000), }, } for name, test := range tests { @@ -92,7 +134,7 @@ func TestGetSettlementPpmWithPerpetual(t *testing.T) { test.index, ) require.Equal(t, test.expectedNetSettlementPpm, netSettlementPpm) - require.Equal(t, test.expectedNewFundingIndex, newFundingIndex) + require.Equal(t, test.perpetual.FundingIndex.BigInt(), newFundingIndex) }) } } @@ -150,15 +192,15 @@ func TestGetNetCollateralAndMarginRequirements(t *testing.T) { test.liquidityTier, test.quantums, ) - nc, imr, mmr := lib.GetNetCollateralAndMarginRequirements( + risk := lib.GetNetCollateralAndMarginRequirements( test.perpetual, test.marketPrice, test.liquidityTier, test.quantums, ) - require.Equal(t, enc, nc) - require.Equal(t, eimr, imr) - require.Equal(t, emmr, mmr) + require.Equal(t, enc, risk.NC) + require.Equal(t, eimr, risk.IMR) + require.Equal(t, emmr, risk.MMR) }) } } diff --git a/protocol/x/perpetuals/types/errors.go b/protocol/x/perpetuals/types/errors.go index a8c179d57b..2fbc53bd53 100644 --- a/protocol/x/perpetuals/types/errors.go +++ b/protocol/x/perpetuals/types/errors.go @@ -122,6 +122,11 @@ var ( 25, "open interest would become negative after update", ) + ErrPerpetualInfoDoesNotExist = errorsmod.Register( + ModuleName, + 26, + "PerpetualInfo does not exist", + ) // Errors for Not Implemented ErrNotImplementedFunding = errorsmod.Register(ModuleName, 1001, "Not Implemented: Perpetuals Funding") diff --git a/protocol/x/perpetuals/types/perpinfo.go b/protocol/x/perpetuals/types/perpinfo.go index 788143aa82..2dbda6106c 100644 --- a/protocol/x/perpetuals/types/perpinfo.go +++ b/protocol/x/perpetuals/types/perpinfo.go @@ -1,6 +1,7 @@ package types import ( + errorsmod "cosmossdk.io/errors" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ) @@ -10,3 +11,21 @@ type PerpInfo struct { Price pricestypes.MarketPrice LiquidityTier LiquidityTier } + +// PerpInfos is a map of PerpInfo objects, keyed by perpetualId. +type PerpInfos map[uint32]PerpInfo + +// MustGet returns the PerpInfo for the given perpetualId, or panics if it does not exist. +func (pi PerpInfos) MustGet(perpetualId uint32) PerpInfo { + p, ok := pi[perpetualId] + + if !ok { + panic(errorsmod.Wrapf( + ErrPerpetualInfoDoesNotExist, + "perpetualId: %d", + perpetualId, + )) + } + + return p +} diff --git a/protocol/x/revshare/client/cli/query.go b/protocol/x/revshare/client/cli/query.go new file mode 100644 index 0000000000..d218c26234 --- /dev/null +++ b/protocol/x/revshare/client/cli/query.go @@ -0,0 +1,24 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +// GetQueryCmd returns the cli query commands for this module. +func GetQueryCmd(queryRoute string) *cobra.Command { + // Group x/revshare queries under a subcommand. + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + return cmd +} diff --git a/protocol/x/revshare/client/cli/tx.go b/protocol/x/revshare/client/cli/tx.go new file mode 100644 index 0000000000..e922972066 --- /dev/null +++ b/protocol/x/revshare/client/cli/tx.go @@ -0,0 +1,23 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +// GetTxCmd returns the transaction commands for this module. +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + return cmd +} diff --git a/protocol/x/revshare/genesis.go b/protocol/x/revshare/genesis.go new file mode 100644 index 0000000000..279196737b --- /dev/null +++ b/protocol/x/revshare/genesis.go @@ -0,0 +1,22 @@ +package revshare + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +// InitGenesis initializes the module's state from a provided genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + k.InitializeForGenesis(ctx) + + if err := k.SetMarketMapperRevenueShareParams(ctx, genState.Params); err != nil { + panic(err) + } +} + +// ExportGenesis returns the module's exported genesis. +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + genesis := types.DefaultGenesis() + return genesis +} diff --git a/protocol/x/revshare/keeper/grpc_query.go b/protocol/x/revshare/keeper/grpc_query.go new file mode 100644 index 0000000000..b7e9c56acf --- /dev/null +++ b/protocol/x/revshare/keeper/grpc_query.go @@ -0,0 +1,7 @@ +package keeper + +import ( + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +var _ types.QueryServer = Keeper{} diff --git a/protocol/x/revshare/keeper/grpc_query_marketmapper_revenue_params.go b/protocol/x/revshare/keeper/grpc_query_marketmapper_revenue_params.go new file mode 100644 index 0000000000..506c495915 --- /dev/null +++ b/protocol/x/revshare/keeper/grpc_query_marketmapper_revenue_params.go @@ -0,0 +1,18 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +func (k Keeper) MarketMapperRevenueShareParams( + ctx context.Context, + req *types.QueryMarketMapperRevenueShareParams, +) (*types.QueryMarketMapperRevenueShareParamsResponse, error) { + params := k.GetMarketMapperRevenueShareParams(sdk.UnwrapSDKContext(ctx)) + return &types.QueryMarketMapperRevenueShareParamsResponse{ + Params: params, + }, nil +} diff --git a/protocol/x/revshare/keeper/grpc_query_marketmapper_revenue_params_test.go b/protocol/x/revshare/keeper/grpc_query_marketmapper_revenue_params_test.go new file mode 100644 index 0000000000..f2dfb13b60 --- /dev/null +++ b/protocol/x/revshare/keeper/grpc_query_marketmapper_revenue_params_test.go @@ -0,0 +1,29 @@ +package keeper_test + +import ( + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" + "github.com/stretchr/testify/require" +) + +func TestQueryMarketMapperRevenueParams(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.RevShareKeeper + + params := types.MarketMapperRevenueShareParams{ + Address: constants.AliceAccAddress.String(), + RevenueSharePpm: 100_000, + ValidDays: 100, + } + + err := k.SetMarketMapperRevenueShareParams(ctx, params) + require.NoError(t, err) + + resp, err := k.MarketMapperRevenueShareParams(ctx, &types.QueryMarketMapperRevenueShareParams{}) + require.NoError(t, err) + require.Equal(t, resp.Params, params) +} diff --git a/protocol/x/revshare/keeper/grpc_query_marketmapper_revshare_details.go b/protocol/x/revshare/keeper/grpc_query_marketmapper_revshare_details.go new file mode 100644 index 0000000000..bef308452f --- /dev/null +++ b/protocol/x/revshare/keeper/grpc_query_marketmapper_revshare_details.go @@ -0,0 +1,21 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +func (k Keeper) MarketMapperRevShareDetails( + ctx context.Context, + req *types.QueryMarketMapperRevShareDetails, +) (*types.QueryMarketMapperRevShareDetailsResponse, error) { + revShareDetails, err := k.GetMarketMapperRevShareDetails(sdk.UnwrapSDKContext(ctx), req.MarketId) + if err != nil { + return nil, err + } + return &types.QueryMarketMapperRevShareDetailsResponse{ + Details: revShareDetails, + }, nil +} diff --git a/protocol/x/revshare/keeper/grpc_query_marketmapper_revshare_details_test.go b/protocol/x/revshare/keeper/grpc_query_marketmapper_revshare_details_test.go new file mode 100644 index 0000000000..426e79f341 --- /dev/null +++ b/protocol/x/revshare/keeper/grpc_query_marketmapper_revshare_details_test.go @@ -0,0 +1,44 @@ +package keeper_test + +import ( + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" + "github.com/stretchr/testify/require" +) + +func TestQueryMarketMapperRevShareDetails(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + + k := tApp.App.RevShareKeeper + + setDetails := types.MarketMapperRevShareDetails{ + ExpirationTs: 1735707600, + } + marketId := uint32(42) + k.SetMarketMapperRevShareDetails(ctx, marketId, setDetails) + + resp, err := k.MarketMapperRevShareDetails( + ctx, &types.QueryMarketMapperRevShareDetails{ + MarketId: marketId, + }, + ) + require.NoError(t, err) + require.Equal(t, resp.Details.ExpirationTs, setDetails.ExpirationTs) +} + +func TestQueryMarketMapperRevShareDetailsFailure(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.RevShareKeeper + + // Query for revshare details of non-existent market + _, err := k.MarketMapperRevShareDetails( + ctx, &types.QueryMarketMapperRevShareDetails{ + MarketId: 42, + }, + ) + require.ErrorContains(t, err, "MarketMapperRevShareDetails not found for marketId: 42") +} diff --git a/protocol/x/revshare/keeper/keeper.go b/protocol/x/revshare/keeper/keeper.go new file mode 100644 index 0000000000..0ae10090b4 --- /dev/null +++ b/protocol/x/revshare/keeper/keeper.go @@ -0,0 +1,43 @@ +package keeper + +import ( + "fmt" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +type ( + Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + authorities map[string]struct{} + } +) + +func NewKeeper( + cdc codec.BinaryCodec, + storeKey storetypes.StoreKey, + authorities []string, +) *Keeper { + return &Keeper{ + cdc: cdc, + storeKey: storeKey, + authorities: lib.UniqueSliceToSet(authorities), + } +} + +func (k Keeper) HasAuthority(authority string) bool { + _, ok := k.authorities[authority] + return ok +} + +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With(log.ModuleKey, fmt.Sprintf("x/%s", types.ModuleName)) +} + +func (k Keeper) InitializeForGenesis(ctx sdk.Context) {} diff --git a/protocol/x/revshare/keeper/keeper_test.go b/protocol/x/revshare/keeper/keeper_test.go new file mode 100644 index 0000000000..9c3c803630 --- /dev/null +++ b/protocol/x/revshare/keeper/keeper_test.go @@ -0,0 +1,16 @@ +package keeper_test + +import ( + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/stretchr/testify/require" +) + +func TestLogger(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + + logger := tApp.App.VaultKeeper.Logger(ctx) + require.NotNil(t, logger) +} diff --git a/protocol/x/revshare/keeper/msg_server.go b/protocol/x/revshare/keeper/msg_server.go new file mode 100644 index 0000000000..f4c769767b --- /dev/null +++ b/protocol/x/revshare/keeper/msg_server.go @@ -0,0 +1,17 @@ +package keeper + +import ( + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} diff --git a/protocol/x/revshare/keeper/msg_server_test.go b/protocol/x/revshare/keeper/msg_server_test.go new file mode 100644 index 0000000000..5b19004a2c --- /dev/null +++ b/protocol/x/revshare/keeper/msg_server_test.go @@ -0,0 +1,26 @@ +package keeper_test + +import ( + "context" + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" + "github.com/stretchr/testify/require" +) + +func setupMsgServer(t *testing.T) (keeper.Keeper, types.MsgServer, context.Context) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.RevShareKeeper + + return k, keeper.NewMsgServerImpl(k), ctx +} + +func TestMsgServer(t *testing.T) { + k, ms, ctx := setupMsgServer(t) + require.NotNil(t, k) + require.NotNil(t, ms) + require.NotNil(t, ctx) +} diff --git a/protocol/x/revshare/keeper/msg_set_marketmapper_revenue_share.go b/protocol/x/revshare/keeper/msg_set_marketmapper_revenue_share.go new file mode 100644 index 0000000000..c1a838a4ec --- /dev/null +++ b/protocol/x/revshare/keeper/msg_set_marketmapper_revenue_share.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +func (k msgServer) SetMarketMapperRevenueShare( + goCtx context.Context, + msg *types.MsgSetMarketMapperRevenueShare, +) (*types.MsgSetMarketMapperRevenueShareResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check if sender is authorized to set revenue share + if !k.HasAuthority(msg.Authority) { + return nil, errorsmod.Wrapf( + govtypes.ErrInvalidSigner, + "invalid authority %s", + msg.Authority, + ) + } + + // Set market mapper revenue share + if err := k.SetMarketMapperRevenueShareParams(ctx, msg.Params); err != nil { + return nil, err + } + + return &types.MsgSetMarketMapperRevenueShareResponse{}, nil +} diff --git a/protocol/x/revshare/keeper/msg_set_marketmapper_revenue_share_test.go b/protocol/x/revshare/keeper/msg_set_marketmapper_revenue_share_test.go new file mode 100644 index 0000000000..520d9e5d89 --- /dev/null +++ b/protocol/x/revshare/keeper/msg_set_marketmapper_revenue_share_test.go @@ -0,0 +1,97 @@ +package keeper_test + +import ( + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/keeper" + "github.com/stretchr/testify/require" + + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +func TestSetMarketMapperRevenueShareParams(t *testing.T) { + tests := map[string]struct { + // Msg + msg *types.MsgSetMarketMapperRevenueShare + // Expected error + expectedErr string + }{ + "Success - Set revenue share": { + msg: &types.MsgSetMarketMapperRevenueShare{ + Authority: lib.GovModuleAddress.String(), + Params: types.MarketMapperRevenueShareParams{ + Address: constants.AliceAccAddress.String(), + RevenueSharePpm: 100_000, + ValidDays: 240, + }, + }, + expectedErr: "", + }, + "Failure - Invalid Authority": { + msg: &types.MsgSetMarketMapperRevenueShare{ + Authority: constants.AliceAccAddress.String(), + Params: types.MarketMapperRevenueShareParams{ + Address: constants.AliceAccAddress.String(), + RevenueSharePpm: 100_000, + ValidDays: 240, + }, + }, + expectedErr: "invalid authority", + }, + "Failure - Empty Authority": { + msg: &types.MsgSetMarketMapperRevenueShare{ + Params: types.MarketMapperRevenueShareParams{ + Address: constants.AliceAccAddress.String(), + RevenueSharePpm: 100_000, + ValidDays: 240, + }, + }, + expectedErr: "invalid authority", + }, + "Failure - Invalid revenue share address": { + msg: &types.MsgSetMarketMapperRevenueShare{ + Authority: lib.GovModuleAddress.String(), + Params: types.MarketMapperRevenueShareParams{ + Address: "invalid_address", + RevenueSharePpm: 100_000, + ValidDays: 240, + }, + }, + expectedErr: "invalid address", + }, + "Failure - Invalid revenue share ppm": { + msg: &types.MsgSetMarketMapperRevenueShare{ + Authority: lib.GovModuleAddress.String(), + Params: types.MarketMapperRevenueShareParams{ + Address: constants.AliceAccAddress.String(), + RevenueSharePpm: 1_000_000, + ValidDays: 240, + }, + }, + expectedErr: "invalid revenue share ppm", + }, + } + + for name, tc := range tests { + t.Run( + name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.RevShareKeeper + ms := keeper.NewMsgServerImpl(k) + _, err := ms.SetMarketMapperRevenueShare(ctx, tc.msg) + if tc.expectedErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err) + params := k.GetMarketMapperRevenueShareParams(ctx) + require.Equal(t, tc.msg.Params, params) + } + }, + ) + } +} diff --git a/protocol/x/revshare/keeper/revshare.go b/protocol/x/revshare/keeper/revshare.go new file mode 100644 index 0000000000..9d490cb3bb --- /dev/null +++ b/protocol/x/revshare/keeper/revshare.go @@ -0,0 +1,78 @@ +package keeper + +import ( + "fmt" + + "cosmossdk.io/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +// Function to serialize market mapper revenue share params and store in the module store +func (k Keeper) SetMarketMapperRevenueShareParams( + ctx sdk.Context, + params types.MarketMapperRevenueShareParams, +) (err error) { + // Validate the params + if err := params.Validate(); err != nil { + return err + } + + // Store the params in the module store + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshal(¶ms) + store.Set([]byte(types.MarketMapperRevenueShareParamsKey), b) + + return nil +} + +// Function to get market mapper revenue share params from the module store +func (k Keeper) GetMarketMapperRevenueShareParams( + ctx sdk.Context, +) (params types.MarketMapperRevenueShareParams) { + store := ctx.KVStore(k.storeKey) + b := store.Get([]byte(types.MarketMapperRevenueShareParamsKey)) + k.cdc.MustUnmarshal(b, ¶ms) + return params +} + +// Function to serialize market mapper rev share details for a market +// and store in the module store +func (k Keeper) SetMarketMapperRevShareDetails( + ctx sdk.Context, + marketId uint32, + params types.MarketMapperRevShareDetails, +) { + // Store the rev share details for provided market in module store + store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.MarketMapperRevSharePrefix)) + b := k.cdc.MustMarshal(¶ms) + store.Set(lib.Uint32ToKey(marketId), b) +} + +// Function to retrieve marketmapper revshare details for a market from module store +func (k Keeper) GetMarketMapperRevShareDetails( + ctx sdk.Context, + marketId uint32, +) (params types.MarketMapperRevShareDetails, err error) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.MarketMapperRevSharePrefix)) + b := store.Get(lib.Uint32ToKey(marketId)) + if b == nil { + return params, fmt.Errorf("MarketMapperRevShareDetails not found for marketId: %d", marketId) + } + k.cdc.MustUnmarshal(b, ¶ms) + return params, nil +} + +// Function to perform all market creation actions for the revshare module +func (k Keeper) CreateNewMarketRevShare(ctx sdk.Context, marketId uint32) { + revShareParams := k.GetMarketMapperRevenueShareParams(ctx) + + validDurationSeconds := int64(revShareParams.ValidDays * 24 * 60 * 60) + + // set the rev share details for the market + details := types.MarketMapperRevShareDetails{ + ExpirationTs: uint64(ctx.BlockTime().Unix() + validDurationSeconds), + } + k.SetMarketMapperRevShareDetails(ctx, marketId, details) +} diff --git a/protocol/x/revshare/keeper/revshare_test.go b/protocol/x/revshare/keeper/revshare_test.go new file mode 100644 index 0000000000..b9bbb8fe17 --- /dev/null +++ b/protocol/x/revshare/keeper/revshare_test.go @@ -0,0 +1,77 @@ +package keeper_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" + "github.com/stretchr/testify/require" +) + +func TestGetSetMarketMapperRevShareDetails(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + + k := tApp.App.RevShareKeeper + // Set the rev share details for a market + marketId := uint32(42) + setDetails := types.MarketMapperRevShareDetails{ + ExpirationTs: 1735707600, + } + k.SetMarketMapperRevShareDetails(ctx, marketId, setDetails) + + // Get the rev share details for the market + getDetails, err := k.GetMarketMapperRevShareDetails(ctx, marketId) + require.NoError(t, err) + require.Equal(t, getDetails.ExpirationTs, setDetails.ExpirationTs) + + // Set expiration ts to 0 + setDetails.ExpirationTs = 0 + k.SetMarketMapperRevShareDetails(ctx, marketId, setDetails) + + getDetails, err = k.GetMarketMapperRevShareDetails(ctx, marketId) + require.NoError(t, err) + require.Equal(t, getDetails.ExpirationTs, setDetails.ExpirationTs) +} + +func TestGetMarketMapperRevShareDetailsFailure(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.RevShareKeeper + + // Get the rev share details for non-existent market + _, err := k.GetMarketMapperRevShareDetails(ctx, 42) + require.ErrorContains(t, err, "MarketMapperRevShareDetails not found for marketId: 42") +} + +func TestCreateNewMarketRevShare(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + + k := tApp.App.RevShareKeeper + + // Set base rev share params + err := k.SetMarketMapperRevenueShareParams( + ctx, types.MarketMapperRevenueShareParams{ + Address: constants.AliceAccAddress.String(), + RevenueSharePpm: 100_000, // 10% + ValidDays: 240, + }, + ) + require.NoError(t, err) + + // Create a new market rev share + marketId := uint32(42) + k.CreateNewMarketRevShare(ctx, marketId) + require.NoError(t, err) + + // Check if the market rev share exists + details, err := k.GetMarketMapperRevShareDetails(ctx, marketId) + require.NoError(t, err) + + // TODO: is this blocktime call deterministic? + expectedExpirationTs := ctx.BlockTime().Unix() + 240*24*60*60 + require.Equal(t, details.ExpirationTs, uint64(expectedExpirationTs)) +} diff --git a/protocol/x/revshare/module.go b/protocol/x/revshare/module.go new file mode 100644 index 0000000000..460b204145 --- /dev/null +++ b/protocol/x/revshare/module.go @@ -0,0 +1,143 @@ +package revshare + +import ( + "encoding/json" + "fmt" + + "cosmossdk.io/core/appmodule" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/client/cli" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" +) + +var ( + _ module.AppModuleBasic = AppModuleBasic{} + _ module.HasGenesisBasics = AppModuleBasic{} + + _ appmodule.AppModule = AppModule{} + _ module.HasConsensusVersion = AppModule{} + _ module.HasGenesis = AppModule{} + _ module.HasServices = AppModule{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module +// needs to implement. +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +// Name returns the name of the module as a string. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs +// to/from []byte in order to persist them in the module's KVStore. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message. +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns a default GenesisState for the module, marshalled to json.RawMessage. The default +// GenesisState need to be defined by the module developer and is primarily used for testing. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return genState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to +// generate new transactions containing messages defined in the module. +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by +// end-users to generate new queries to the subset of the state defined by the module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd(types.StoreKey) +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement. +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper +} + +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + } +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// IsOnePerModuleType is a marker function just indicates that this is a one-per-module type. +func (am AppModule) IsOnePerModuleType() {} + +// RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// InitGenesis performs the module's genesis initialization. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { + var genState types.GenesisState + // Initialize global index to index in genesis state. + cdc.MustUnmarshalJSON(gs, &genState) + + InitGenesis(ctx, am.keeper, genState) +} + +// ExportGenesis returns the module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each +// consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should +// be set to 1. +func (AppModule) ConsensusVersion() uint64 { return 1 } diff --git a/protocol/x/revshare/types/codec.go b/protocol/x/revshare/types/codec.go new file mode 100644 index 0000000000..b68229b0d6 --- /dev/null +++ b/protocol/x/revshare/types/codec.go @@ -0,0 +1,19 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/dydxprotocol/v4-chain/protocol/app/module" +) + +func RegisterCodec(cdc *codec.LegacyAmino) {} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + Amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewProtoCodec(module.InterfaceRegistry) +) diff --git a/protocol/x/revshare/types/errors.go b/protocol/x/revshare/types/errors.go new file mode 100644 index 0000000000..cf9b53cbe5 --- /dev/null +++ b/protocol/x/revshare/types/errors.go @@ -0,0 +1,17 @@ +package types + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrInvalidAddress = errorsmod.Register( + ModuleName, + 1, + "invalid address", + ) + + ErrInvalidRevenueSharePpm = errorsmod.Register( + ModuleName, + 2, + "invalid revenue share ppm", + ) +) diff --git a/protocol/x/revshare/types/expected_keepers.go b/protocol/x/revshare/types/expected_keepers.go new file mode 100644 index 0000000000..ab1254f4c2 --- /dev/null +++ b/protocol/x/revshare/types/expected_keepers.go @@ -0,0 +1 @@ +package types diff --git a/protocol/x/revshare/types/genesis.go b/protocol/x/revshare/types/genesis.go new file mode 100644 index 0000000000..fa21b9ab96 --- /dev/null +++ b/protocol/x/revshare/types/genesis.go @@ -0,0 +1,14 @@ +package types + +// DefaultGenesis returns the default stats genesis state. +func DefaultGenesis() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + return nil +} diff --git a/protocol/x/revshare/types/genesis.pb.go b/protocol/x/revshare/types/genesis.pb.go new file mode 100644 index 0000000000..2c8f235ebd --- /dev/null +++ b/protocol/x/revshare/types/genesis.pb.go @@ -0,0 +1,324 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: dydxprotocol/revshare/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines `x/revshare`'s genesis state. +type GenesisState struct { + Params MarketMapperRevenueShareParams `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_f930be52e9995581, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() MarketMapperRevenueShareParams { + if m != nil { + return m.Params + } + return MarketMapperRevenueShareParams{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "dydxprotocol.revshare.GenesisState") +} + +func init() { + proto.RegisterFile("dydxprotocol/revshare/genesis.proto", fileDescriptor_f930be52e9995581) +} + +var fileDescriptor_f930be52e9995581 = []byte{ + // 221 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4e, 0xa9, 0x4c, 0xa9, + 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4a, 0x2d, 0x2b, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x03, 0xcb, 0x08, 0x89, 0x22, 0x2b, + 0xd2, 0x83, 0x29, 0x92, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x0b, 0xeb, 0x83, 0x58, 0x10, 0xc5, + 0x52, 0x4a, 0xd8, 0x4d, 0x2c, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0x1a, 0xa8, 0x94, 0xcc, 0xc5, 0xe3, + 0x0e, 0xb1, 0x21, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0x28, 0x98, 0x8b, 0x0d, 0x22, 0x2f, 0xc1, 0xa8, + 0xc0, 0xa8, 0xc1, 0x6d, 0x64, 0xaa, 0x87, 0xd5, 0x46, 0x3d, 0xdf, 0xc4, 0xa2, 0xec, 0xd4, 0x12, + 0xdf, 0xc4, 0x82, 0x82, 0xd4, 0xa2, 0xa0, 0xd4, 0xb2, 0xd4, 0xbc, 0xd2, 0xd4, 0x60, 0x90, 0x44, + 0x00, 0x58, 0xb3, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, 0xa3, 0x9c, 0x42, 0x4e, 0x3c, + 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, + 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x2a, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, + 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xc5, 0xb5, 0x65, 0x26, 0xba, 0xc9, 0x19, 0x89, 0x99, 0x79, 0xfa, + 0x70, 0x91, 0x0a, 0x84, 0x0f, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x52, 0xc6, 0x80, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x3b, 0x5c, 0x0c, 0x39, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/revshare/types/keys.go b/protocol/x/revshare/types/keys.go new file mode 100644 index 0000000000..375536e917 --- /dev/null +++ b/protocol/x/revshare/types/keys.go @@ -0,0 +1,19 @@ +package types + +// Module name and store keys. +const ( + // ModuleName defines the module name. + ModuleName = "revshare" + + // StoreKey defines the primary module store key. + StoreKey = ModuleName +) + +// State +const ( + // Key for MarketMapperRevenueShareParams + MarketMapperRevenueShareParamsKey = "MarketMapperRevenueShareParams" + + // Key prefix for storing MarketMapperRevShareDetails per market + MarketMapperRevSharePrefix = "MarketMapperRevShare:" +) diff --git a/protocol/x/revshare/types/params.go b/protocol/x/revshare/types/params.go new file mode 100644 index 0000000000..3b82c9fad3 --- /dev/null +++ b/protocol/x/revshare/types/params.go @@ -0,0 +1,31 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// DefaultParams returns a default set of `x/revshare` market mapper parameters. +func DefaultParams() MarketMapperRevenueShareParams { + return MarketMapperRevenueShareParams{ + Address: authtypes.NewModuleAddress(authtypes.FeeCollectorName).String(), + RevenueSharePpm: 0, + ValidDays: 0, + } +} + +// Validate validates `x/revshare` parameters. +func (p MarketMapperRevenueShareParams) Validate() error { + // Address must be a valid address + _, err := types.AccAddressFromBech32(p.Address) + if err != nil { + return ErrInvalidAddress + } + + // Revenue share ppm must be less than 1000000 (100%) + if p.RevenueSharePpm >= 1000000 { + return ErrInvalidRevenueSharePpm + } + + return nil +} diff --git a/protocol/x/revshare/types/params.pb.go b/protocol/x/revshare/types/params.pb.go new file mode 100644 index 0000000000..86f4f357b3 --- /dev/null +++ b/protocol/x/revshare/types/params.pb.go @@ -0,0 +1,401 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: dydxprotocol/revshare/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MarketMappeRevenueShareParams represents params for the above message +type MarketMapperRevenueShareParams struct { + // The address which will receive the revenue share payouts + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // The fraction of the fees which will go to the above mentioned address. + // In parts-per-million + RevenueSharePpm uint32 `protobuf:"varint,2,opt,name=revenue_share_ppm,json=revenueSharePpm,proto3" json:"revenue_share_ppm,omitempty"` + // This parameter defines how many days post market initiation will the + // revenue share be applied for. After valid_days from market initiation + // the revenue share goes down to 0 + ValidDays uint32 `protobuf:"varint,3,opt,name=valid_days,json=validDays,proto3" json:"valid_days,omitempty"` +} + +func (m *MarketMapperRevenueShareParams) Reset() { *m = MarketMapperRevenueShareParams{} } +func (m *MarketMapperRevenueShareParams) String() string { return proto.CompactTextString(m) } +func (*MarketMapperRevenueShareParams) ProtoMessage() {} +func (*MarketMapperRevenueShareParams) Descriptor() ([]byte, []int) { + return fileDescriptor_df3e7303a76b1505, []int{0} +} +func (m *MarketMapperRevenueShareParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MarketMapperRevenueShareParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MarketMapperRevenueShareParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MarketMapperRevenueShareParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketMapperRevenueShareParams.Merge(m, src) +} +func (m *MarketMapperRevenueShareParams) XXX_Size() int { + return m.Size() +} +func (m *MarketMapperRevenueShareParams) XXX_DiscardUnknown() { + xxx_messageInfo_MarketMapperRevenueShareParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketMapperRevenueShareParams proto.InternalMessageInfo + +func (m *MarketMapperRevenueShareParams) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *MarketMapperRevenueShareParams) GetRevenueSharePpm() uint32 { + if m != nil { + return m.RevenueSharePpm + } + return 0 +} + +func (m *MarketMapperRevenueShareParams) GetValidDays() uint32 { + if m != nil { + return m.ValidDays + } + return 0 +} + +func init() { + proto.RegisterType((*MarketMapperRevenueShareParams)(nil), "dydxprotocol.revshare.MarketMapperRevenueShareParams") +} + +func init() { + proto.RegisterFile("dydxprotocol/revshare/params.proto", fileDescriptor_df3e7303a76b1505) +} + +var fileDescriptor_df3e7303a76b1505 = []byte{ + // 267 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0xa9, 0x4c, 0xa9, + 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4a, 0x2d, 0x2b, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x03, 0x4b, 0x08, 0x89, 0x22, 0xab, 0xd1, + 0x83, 0xa9, 0x91, 0x92, 0x4c, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0x8e, 0x07, 0xcb, 0xe8, 0x43, 0x38, + 0x10, 0x1d, 0x4a, 0xf3, 0x19, 0xb9, 0xe4, 0x7c, 0x13, 0x8b, 0xb2, 0x53, 0x4b, 0x7c, 0x13, 0x0b, + 0x0a, 0x52, 0x8b, 0x82, 0x52, 0xcb, 0x52, 0xf3, 0x4a, 0x53, 0x83, 0x41, 0xfa, 0x02, 0xc0, 0x46, + 0x0b, 0x19, 0x71, 0xb1, 0x27, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0x4b, 0x30, 0x2a, 0x30, 0x6a, + 0x70, 0x3a, 0x49, 0x5c, 0xda, 0xa2, 0x2b, 0x02, 0x35, 0xc5, 0x11, 0x22, 0x13, 0x5c, 0x52, 0x94, + 0x99, 0x97, 0x1e, 0x04, 0x53, 0x28, 0xa4, 0xc5, 0x25, 0x58, 0x04, 0x31, 0x29, 0x1e, 0xec, 0x84, + 0xf8, 0x82, 0x82, 0x5c, 0x09, 0x26, 0x05, 0x46, 0x0d, 0xde, 0x20, 0xfe, 0x22, 0x64, 0x2b, 0x0a, + 0x72, 0x85, 0x64, 0xb9, 0xb8, 0xca, 0x12, 0x73, 0x32, 0x53, 0xe2, 0x53, 0x12, 0x2b, 0x8b, 0x25, + 0x98, 0xc1, 0x8a, 0x38, 0xc1, 0x22, 0x2e, 0x89, 0x95, 0xc5, 0x4e, 0x21, 0x27, 0x1e, 0xc9, 0x31, + 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, + 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x95, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, + 0x9f, 0xab, 0x8f, 0x12, 0x38, 0x65, 0x26, 0xba, 0xc9, 0x19, 0x89, 0x99, 0x79, 0xfa, 0x70, 0x91, + 0x0a, 0x44, 0x80, 0x95, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0xa5, 0x8c, 0x01, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x46, 0xa2, 0x88, 0x4a, 0x56, 0x01, 0x00, 0x00, +} + +func (m *MarketMapperRevenueShareParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MarketMapperRevenueShareParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MarketMapperRevenueShareParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ValidDays != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.ValidDays)) + i-- + dAtA[i] = 0x18 + } + if m.RevenueSharePpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.RevenueSharePpm)) + i-- + dAtA[i] = 0x10 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintParams(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MarketMapperRevenueShareParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovParams(uint64(l)) + } + if m.RevenueSharePpm != 0 { + n += 1 + sovParams(uint64(m.RevenueSharePpm)) + } + if m.ValidDays != 0 { + n += 1 + sovParams(uint64(m.ValidDays)) + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MarketMapperRevenueShareParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MarketMapperRevenueShareParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MarketMapperRevenueShareParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevenueSharePpm", wireType) + } + m.RevenueSharePpm = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevenueSharePpm |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidDays", wireType) + } + m.ValidDays = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ValidDays |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/revshare/types/query.pb.go b/protocol/x/revshare/types/query.pb.go new file mode 100644 index 0000000000..0e8c70fb41 --- /dev/null +++ b/protocol/x/revshare/types/query.pb.go @@ -0,0 +1,917 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: dydxprotocol/revshare/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Queries for the default market mapper revenue share params +type QueryMarketMapperRevenueShareParams struct { +} + +func (m *QueryMarketMapperRevenueShareParams) Reset() { *m = QueryMarketMapperRevenueShareParams{} } +func (m *QueryMarketMapperRevenueShareParams) String() string { return proto.CompactTextString(m) } +func (*QueryMarketMapperRevenueShareParams) ProtoMessage() {} +func (*QueryMarketMapperRevenueShareParams) Descriptor() ([]byte, []int) { + return fileDescriptor_13d50c6e3048e744, []int{0} +} +func (m *QueryMarketMapperRevenueShareParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMarketMapperRevenueShareParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMarketMapperRevenueShareParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMarketMapperRevenueShareParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMarketMapperRevenueShareParams.Merge(m, src) +} +func (m *QueryMarketMapperRevenueShareParams) XXX_Size() int { + return m.Size() +} +func (m *QueryMarketMapperRevenueShareParams) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMarketMapperRevenueShareParams.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMarketMapperRevenueShareParams proto.InternalMessageInfo + +// Response type for QueryMarketMapperRevenueShareParams +type QueryMarketMapperRevenueShareParamsResponse struct { + Params MarketMapperRevenueShareParams `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryMarketMapperRevenueShareParamsResponse) Reset() { + *m = QueryMarketMapperRevenueShareParamsResponse{} +} +func (m *QueryMarketMapperRevenueShareParamsResponse) String() string { + return proto.CompactTextString(m) +} +func (*QueryMarketMapperRevenueShareParamsResponse) ProtoMessage() {} +func (*QueryMarketMapperRevenueShareParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_13d50c6e3048e744, []int{1} +} +func (m *QueryMarketMapperRevenueShareParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMarketMapperRevenueShareParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMarketMapperRevenueShareParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMarketMapperRevenueShareParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMarketMapperRevenueShareParamsResponse.Merge(m, src) +} +func (m *QueryMarketMapperRevenueShareParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryMarketMapperRevenueShareParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMarketMapperRevenueShareParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMarketMapperRevenueShareParamsResponse proto.InternalMessageInfo + +func (m *QueryMarketMapperRevenueShareParamsResponse) GetParams() MarketMapperRevenueShareParams { + if m != nil { + return m.Params + } + return MarketMapperRevenueShareParams{} +} + +// Queries market mapper revenue share details for a specific market +type QueryMarketMapperRevShareDetails struct { + MarketId uint32 `protobuf:"varint,1,opt,name=market_id,json=marketId,proto3" json:"market_id,omitempty"` +} + +func (m *QueryMarketMapperRevShareDetails) Reset() { *m = QueryMarketMapperRevShareDetails{} } +func (m *QueryMarketMapperRevShareDetails) String() string { return proto.CompactTextString(m) } +func (*QueryMarketMapperRevShareDetails) ProtoMessage() {} +func (*QueryMarketMapperRevShareDetails) Descriptor() ([]byte, []int) { + return fileDescriptor_13d50c6e3048e744, []int{2} +} +func (m *QueryMarketMapperRevShareDetails) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMarketMapperRevShareDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMarketMapperRevShareDetails.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMarketMapperRevShareDetails) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMarketMapperRevShareDetails.Merge(m, src) +} +func (m *QueryMarketMapperRevShareDetails) XXX_Size() int { + return m.Size() +} +func (m *QueryMarketMapperRevShareDetails) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMarketMapperRevShareDetails.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMarketMapperRevShareDetails proto.InternalMessageInfo + +func (m *QueryMarketMapperRevShareDetails) GetMarketId() uint32 { + if m != nil { + return m.MarketId + } + return 0 +} + +// Response type for QueryMarketMapperRevShareDetails +type QueryMarketMapperRevShareDetailsResponse struct { + Details MarketMapperRevShareDetails `protobuf:"bytes,1,opt,name=details,proto3" json:"details"` +} + +func (m *QueryMarketMapperRevShareDetailsResponse) Reset() { + *m = QueryMarketMapperRevShareDetailsResponse{} +} +func (m *QueryMarketMapperRevShareDetailsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryMarketMapperRevShareDetailsResponse) ProtoMessage() {} +func (*QueryMarketMapperRevShareDetailsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_13d50c6e3048e744, []int{3} +} +func (m *QueryMarketMapperRevShareDetailsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMarketMapperRevShareDetailsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMarketMapperRevShareDetailsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMarketMapperRevShareDetailsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMarketMapperRevShareDetailsResponse.Merge(m, src) +} +func (m *QueryMarketMapperRevShareDetailsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryMarketMapperRevShareDetailsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMarketMapperRevShareDetailsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMarketMapperRevShareDetailsResponse proto.InternalMessageInfo + +func (m *QueryMarketMapperRevShareDetailsResponse) GetDetails() MarketMapperRevShareDetails { + if m != nil { + return m.Details + } + return MarketMapperRevShareDetails{} +} + +func init() { + proto.RegisterType((*QueryMarketMapperRevenueShareParams)(nil), "dydxprotocol.revshare.QueryMarketMapperRevenueShareParams") + proto.RegisterType((*QueryMarketMapperRevenueShareParamsResponse)(nil), "dydxprotocol.revshare.QueryMarketMapperRevenueShareParamsResponse") + proto.RegisterType((*QueryMarketMapperRevShareDetails)(nil), "dydxprotocol.revshare.QueryMarketMapperRevShareDetails") + proto.RegisterType((*QueryMarketMapperRevShareDetailsResponse)(nil), "dydxprotocol.revshare.QueryMarketMapperRevShareDetailsResponse") +} + +func init() { proto.RegisterFile("dydxprotocol/revshare/query.proto", fileDescriptor_13d50c6e3048e744) } + +var fileDescriptor_13d50c6e3048e744 = []byte{ + // 420 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0xa9, 0x4c, 0xa9, + 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4a, 0x2d, 0x2b, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x03, 0x8b, 0x0b, 0x89, 0x22, 0x2b, 0xd1, 0x83, + 0x29, 0x91, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x0b, 0xeb, 0x83, 0x58, 0x10, 0xc5, 0x52, 0x32, + 0xe9, 0xf9, 0xf9, 0xe9, 0x39, 0xa9, 0xfa, 0x89, 0x05, 0x99, 0xfa, 0x89, 0x79, 0x79, 0xf9, 0x25, + 0x89, 0x25, 0x99, 0xf9, 0x79, 0xc5, 0x50, 0x59, 0x25, 0xec, 0xb6, 0x15, 0x24, 0x16, 0x25, 0xe6, + 0xc2, 0xd4, 0xa8, 0x60, 0x57, 0x03, 0x63, 0x40, 0x54, 0x29, 0xa9, 0x72, 0x29, 0x07, 0x82, 0xdc, + 0xe8, 0x9b, 0x58, 0x94, 0x9d, 0x5a, 0xe2, 0x9b, 0x58, 0x50, 0x90, 0x5a, 0x14, 0x94, 0x5a, 0x96, + 0x9a, 0x57, 0x9a, 0x1a, 0x0c, 0x52, 0x16, 0x00, 0x36, 0x52, 0xa9, 0x89, 0x91, 0x4b, 0x9b, 0x08, + 0x75, 0x41, 0xa9, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0xc1, 0x5c, 0x6c, 0x10, 0xc7, 0x48, + 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x99, 0xea, 0x61, 0xf5, 0xbc, 0x1e, 0x7e, 0xe3, 0x9c, 0x58, + 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0x1a, 0xa5, 0x64, 0xcf, 0xa5, 0x80, 0xcd, 0x0d, 0x60, 0x0d, + 0x2e, 0xa9, 0x25, 0x89, 0x99, 0x39, 0xc5, 0x42, 0xd2, 0x5c, 0x9c, 0xb9, 0x60, 0xe9, 0xf8, 0xcc, + 0x14, 0xb0, 0xdd, 0xbc, 0x41, 0x1c, 0x10, 0x01, 0xcf, 0x14, 0xa5, 0x3a, 0x2e, 0x0d, 0x42, 0x06, + 0xc0, 0x7d, 0x10, 0xc4, 0xc5, 0x9e, 0x02, 0x11, 0x82, 0x7a, 0xc1, 0x88, 0x38, 0x2f, 0x20, 0x1b, + 0x06, 0x75, 0x3f, 0xcc, 0x20, 0xa3, 0xf3, 0xcc, 0x5c, 0xac, 0x60, 0x07, 0x08, 0xdd, 0x67, 0xe4, + 0x92, 0xc3, 0xef, 0x77, 0x21, 0x2b, 0x1c, 0xf6, 0x11, 0x11, 0x0d, 0x52, 0x4e, 0xe4, 0xeb, 0x85, + 0x05, 0x80, 0x92, 0x6d, 0xd3, 0xe5, 0x27, 0x93, 0x99, 0xcc, 0x85, 0x4c, 0xf5, 0xb1, 0x27, 0x24, + 0x68, 0x30, 0xe7, 0x82, 0xcd, 0x89, 0x2f, 0x4a, 0x2d, 0x8b, 0x07, 0x8b, 0xc7, 0x43, 0x22, 0x4b, + 0xe8, 0x31, 0x23, 0x97, 0x34, 0xbe, 0x88, 0x32, 0x27, 0xc1, 0x89, 0xc8, 0x1a, 0xa5, 0xec, 0xc9, + 0xd4, 0x08, 0xf7, 0x98, 0x17, 0xd8, 0x63, 0x2e, 0x42, 0x4e, 0x24, 0x7a, 0x0c, 0x1a, 0x8b, 0xfa, + 0xd5, 0xf0, 0x04, 0x56, 0xeb, 0x14, 0x72, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, + 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, + 0x51, 0x56, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xa8, 0xf6, 0x94, 0x99, + 0xe8, 0x26, 0x67, 0x24, 0x66, 0xe6, 0xe9, 0xc3, 0x45, 0x2a, 0x10, 0x76, 0x97, 0x54, 0x16, 0xa4, + 0x16, 0x27, 0xb1, 0x81, 0xa5, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x31, 0xa4, 0xeb, 0xf4, + 0x55, 0x04, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // MarketMapperRevenueShareParams queries the revenue share params for the + // market mapper + MarketMapperRevenueShareParams(ctx context.Context, in *QueryMarketMapperRevenueShareParams, opts ...grpc.CallOption) (*QueryMarketMapperRevenueShareParamsResponse, error) + // Queries market mapper revenue share details for a specific market + MarketMapperRevShareDetails(ctx context.Context, in *QueryMarketMapperRevShareDetails, opts ...grpc.CallOption) (*QueryMarketMapperRevShareDetailsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) MarketMapperRevenueShareParams(ctx context.Context, in *QueryMarketMapperRevenueShareParams, opts ...grpc.CallOption) (*QueryMarketMapperRevenueShareParamsResponse, error) { + out := new(QueryMarketMapperRevenueShareParamsResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.revshare.Query/MarketMapperRevenueShareParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) MarketMapperRevShareDetails(ctx context.Context, in *QueryMarketMapperRevShareDetails, opts ...grpc.CallOption) (*QueryMarketMapperRevShareDetailsResponse, error) { + out := new(QueryMarketMapperRevShareDetailsResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.revshare.Query/MarketMapperRevShareDetails", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // MarketMapperRevenueShareParams queries the revenue share params for the + // market mapper + MarketMapperRevenueShareParams(context.Context, *QueryMarketMapperRevenueShareParams) (*QueryMarketMapperRevenueShareParamsResponse, error) + // Queries market mapper revenue share details for a specific market + MarketMapperRevShareDetails(context.Context, *QueryMarketMapperRevShareDetails) (*QueryMarketMapperRevShareDetailsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) MarketMapperRevenueShareParams(ctx context.Context, req *QueryMarketMapperRevenueShareParams) (*QueryMarketMapperRevenueShareParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MarketMapperRevenueShareParams not implemented") +} +func (*UnimplementedQueryServer) MarketMapperRevShareDetails(ctx context.Context, req *QueryMarketMapperRevShareDetails) (*QueryMarketMapperRevShareDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MarketMapperRevShareDetails not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_MarketMapperRevenueShareParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryMarketMapperRevenueShareParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).MarketMapperRevenueShareParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.revshare.Query/MarketMapperRevenueShareParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).MarketMapperRevenueShareParams(ctx, req.(*QueryMarketMapperRevenueShareParams)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_MarketMapperRevShareDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryMarketMapperRevShareDetails) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).MarketMapperRevShareDetails(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.revshare.Query/MarketMapperRevShareDetails", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).MarketMapperRevShareDetails(ctx, req.(*QueryMarketMapperRevShareDetails)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "dydxprotocol.revshare.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "MarketMapperRevenueShareParams", + Handler: _Query_MarketMapperRevenueShareParams_Handler, + }, + { + MethodName: "MarketMapperRevShareDetails", + Handler: _Query_MarketMapperRevShareDetails_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "dydxprotocol/revshare/query.proto", +} + +func (m *QueryMarketMapperRevenueShareParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMarketMapperRevenueShareParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMarketMapperRevenueShareParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryMarketMapperRevenueShareParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMarketMapperRevenueShareParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMarketMapperRevenueShareParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryMarketMapperRevShareDetails) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMarketMapperRevShareDetails) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMarketMapperRevShareDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MarketId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.MarketId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryMarketMapperRevShareDetailsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMarketMapperRevShareDetailsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMarketMapperRevShareDetailsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Details.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryMarketMapperRevenueShareParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryMarketMapperRevenueShareParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryMarketMapperRevShareDetails) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MarketId != 0 { + n += 1 + sovQuery(uint64(m.MarketId)) + } + return n +} + +func (m *QueryMarketMapperRevShareDetailsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Details.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryMarketMapperRevenueShareParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMarketMapperRevenueShareParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMarketMapperRevenueShareParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMarketMapperRevenueShareParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMarketMapperRevenueShareParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMarketMapperRevenueShareParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMarketMapperRevShareDetails) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMarketMapperRevShareDetails: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMarketMapperRevShareDetails: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MarketId", wireType) + } + m.MarketId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MarketId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMarketMapperRevShareDetailsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMarketMapperRevShareDetailsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMarketMapperRevShareDetailsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/revshare/types/query.pb.gw.go b/protocol/x/revshare/types/query.pb.gw.go new file mode 100644 index 0000000000..3f2c7025d1 --- /dev/null +++ b/protocol/x/revshare/types/query.pb.gw.go @@ -0,0 +1,254 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: dydxprotocol/revshare/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_MarketMapperRevenueShareParams_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMarketMapperRevenueShareParams + var metadata runtime.ServerMetadata + + msg, err := client.MarketMapperRevenueShareParams(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_MarketMapperRevenueShareParams_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMarketMapperRevenueShareParams + var metadata runtime.ServerMetadata + + msg, err := server.MarketMapperRevenueShareParams(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_MarketMapperRevShareDetails_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMarketMapperRevShareDetails + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["market_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "market_id") + } + + protoReq.MarketId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "market_id", err) + } + + msg, err := client.MarketMapperRevShareDetails(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_MarketMapperRevShareDetails_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMarketMapperRevShareDetails + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["market_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "market_id") + } + + protoReq.MarketId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "market_id", err) + } + + msg, err := server.MarketMapperRevShareDetails(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_MarketMapperRevenueShareParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_MarketMapperRevenueShareParams_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MarketMapperRevenueShareParams_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_MarketMapperRevShareDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_MarketMapperRevShareDetails_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MarketMapperRevShareDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_MarketMapperRevenueShareParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_MarketMapperRevenueShareParams_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MarketMapperRevenueShareParams_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_MarketMapperRevShareDetails_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_MarketMapperRevShareDetails_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MarketMapperRevShareDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_MarketMapperRevenueShareParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"dydxprotocol", "revshare", "market_mapper_rev_share_params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_MarketMapperRevShareDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"dydxprotocol", "revshare", "market_mapper_rev_share_details", "market_id"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_MarketMapperRevenueShareParams_0 = runtime.ForwardResponseMessage + + forward_Query_MarketMapperRevShareDetails_0 = runtime.ForwardResponseMessage +) diff --git a/protocol/x/revshare/types/revshare.pb.go b/protocol/x/revshare/types/revshare.pb.go new file mode 100644 index 0000000000..ae93064e4f --- /dev/null +++ b/protocol/x/revshare/types/revshare.pb.go @@ -0,0 +1,305 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: dydxprotocol/revshare/revshare.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MarketMapperRevShareDetails specifies any details associated with the market +// mapper revenue share +type MarketMapperRevShareDetails struct { + // Unix timestamp recorded when the market revenue share expires + ExpirationTs uint64 `protobuf:"varint,1,opt,name=expiration_ts,json=expirationTs,proto3" json:"expiration_ts,omitempty"` +} + +func (m *MarketMapperRevShareDetails) Reset() { *m = MarketMapperRevShareDetails{} } +func (m *MarketMapperRevShareDetails) String() string { return proto.CompactTextString(m) } +func (*MarketMapperRevShareDetails) ProtoMessage() {} +func (*MarketMapperRevShareDetails) Descriptor() ([]byte, []int) { + return fileDescriptor_5b9759663d195798, []int{0} +} +func (m *MarketMapperRevShareDetails) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MarketMapperRevShareDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MarketMapperRevShareDetails.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MarketMapperRevShareDetails) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketMapperRevShareDetails.Merge(m, src) +} +func (m *MarketMapperRevShareDetails) XXX_Size() int { + return m.Size() +} +func (m *MarketMapperRevShareDetails) XXX_DiscardUnknown() { + xxx_messageInfo_MarketMapperRevShareDetails.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketMapperRevShareDetails proto.InternalMessageInfo + +func (m *MarketMapperRevShareDetails) GetExpirationTs() uint64 { + if m != nil { + return m.ExpirationTs + } + return 0 +} + +func init() { + proto.RegisterType((*MarketMapperRevShareDetails)(nil), "dydxprotocol.revshare.MarketMapperRevShareDetails") +} + +func init() { + proto.RegisterFile("dydxprotocol/revshare/revshare.proto", fileDescriptor_5b9759663d195798) +} + +var fileDescriptor_5b9759663d195798 = []byte{ + // 183 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x49, 0xa9, 0x4c, 0xa9, + 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4a, 0x2d, 0x2b, 0xce, 0x48, 0x2c, + 0x4a, 0x85, 0x33, 0xf4, 0xc0, 0x52, 0x42, 0xa2, 0xc8, 0xaa, 0xf4, 0x60, 0x92, 0x4a, 0x4e, 0x5c, + 0xd2, 0xbe, 0x89, 0x45, 0xd9, 0xa9, 0x25, 0xbe, 0x89, 0x05, 0x05, 0xa9, 0x45, 0x41, 0xa9, 0x65, + 0xc1, 0x20, 0x71, 0x97, 0xd4, 0x92, 0xc4, 0xcc, 0x9c, 0x62, 0x21, 0x65, 0x2e, 0xde, 0xd4, 0x8a, + 0x82, 0xcc, 0xa2, 0xc4, 0x92, 0xcc, 0xfc, 0xbc, 0xf8, 0x92, 0x62, 0x09, 0x46, 0x05, 0x46, 0x0d, + 0x96, 0x20, 0x1e, 0x84, 0x60, 0x48, 0xb1, 0x53, 0xc8, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, + 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, + 0xcb, 0x31, 0x44, 0x59, 0xa5, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xa3, + 0xb8, 0xb2, 0xcc, 0x44, 0x37, 0x39, 0x23, 0x31, 0x33, 0x4f, 0x1f, 0x2e, 0x52, 0x81, 0x70, 0x79, + 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x58, 0xca, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xec, + 0xc5, 0x93, 0xd9, 0xdf, 0x00, 0x00, 0x00, +} + +func (m *MarketMapperRevShareDetails) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MarketMapperRevShareDetails) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MarketMapperRevShareDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ExpirationTs != 0 { + i = encodeVarintRevshare(dAtA, i, uint64(m.ExpirationTs)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintRevshare(dAtA []byte, offset int, v uint64) int { + offset -= sovRevshare(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MarketMapperRevShareDetails) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ExpirationTs != 0 { + n += 1 + sovRevshare(uint64(m.ExpirationTs)) + } + return n +} + +func sovRevshare(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozRevshare(x uint64) (n int) { + return sovRevshare(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MarketMapperRevShareDetails) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRevshare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MarketMapperRevShareDetails: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MarketMapperRevShareDetails: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpirationTs", wireType) + } + m.ExpirationTs = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRevshare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpirationTs |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipRevshare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRevshare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRevshare(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRevshare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRevshare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRevshare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthRevshare + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupRevshare + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthRevshare + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthRevshare = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRevshare = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupRevshare = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/revshare/types/tx.pb.go b/protocol/x/revshare/types/tx.pb.go new file mode 100644 index 0000000000..7f166b10f5 --- /dev/null +++ b/protocol/x/revshare/types/tx.pb.go @@ -0,0 +1,594 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: dydxprotocol/revshare/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Message to set the market mapper revenue share +type MsgSetMarketMapperRevenueShare struct { + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // Parameters for the revenue share + Params MarketMapperRevenueShareParams `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgSetMarketMapperRevenueShare) Reset() { *m = MsgSetMarketMapperRevenueShare{} } +func (m *MsgSetMarketMapperRevenueShare) String() string { return proto.CompactTextString(m) } +func (*MsgSetMarketMapperRevenueShare) ProtoMessage() {} +func (*MsgSetMarketMapperRevenueShare) Descriptor() ([]byte, []int) { + return fileDescriptor_460d8062a262197e, []int{0} +} +func (m *MsgSetMarketMapperRevenueShare) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetMarketMapperRevenueShare) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetMarketMapperRevenueShare.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetMarketMapperRevenueShare) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetMarketMapperRevenueShare.Merge(m, src) +} +func (m *MsgSetMarketMapperRevenueShare) XXX_Size() int { + return m.Size() +} +func (m *MsgSetMarketMapperRevenueShare) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetMarketMapperRevenueShare.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetMarketMapperRevenueShare proto.InternalMessageInfo + +func (m *MsgSetMarketMapperRevenueShare) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgSetMarketMapperRevenueShare) GetParams() MarketMapperRevenueShareParams { + if m != nil { + return m.Params + } + return MarketMapperRevenueShareParams{} +} + +// Response to a MsgSetMarketMapperRevenueShare +type MsgSetMarketMapperRevenueShareResponse struct { +} + +func (m *MsgSetMarketMapperRevenueShareResponse) Reset() { + *m = MsgSetMarketMapperRevenueShareResponse{} +} +func (m *MsgSetMarketMapperRevenueShareResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSetMarketMapperRevenueShareResponse) ProtoMessage() {} +func (*MsgSetMarketMapperRevenueShareResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_460d8062a262197e, []int{1} +} +func (m *MsgSetMarketMapperRevenueShareResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetMarketMapperRevenueShareResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetMarketMapperRevenueShareResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetMarketMapperRevenueShareResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetMarketMapperRevenueShareResponse.Merge(m, src) +} +func (m *MsgSetMarketMapperRevenueShareResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSetMarketMapperRevenueShareResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetMarketMapperRevenueShareResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetMarketMapperRevenueShareResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgSetMarketMapperRevenueShare)(nil), "dydxprotocol.revshare.MsgSetMarketMapperRevenueShare") + proto.RegisterType((*MsgSetMarketMapperRevenueShareResponse)(nil), "dydxprotocol.revshare.MsgSetMarketMapperRevenueShareResponse") +} + +func init() { proto.RegisterFile("dydxprotocol/revshare/tx.proto", fileDescriptor_460d8062a262197e) } + +var fileDescriptor_460d8062a262197e = []byte{ + // 335 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0xa9, 0x4c, 0xa9, + 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4a, 0x2d, 0x2b, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x2f, 0xa9, 0xd0, 0x03, 0x0b, 0x0a, 0x89, 0x22, 0xcb, 0xeb, 0xc1, 0xe4, 0xa5, 0x24, + 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xe3, 0xc1, 0x32, 0xfa, 0x10, 0x0e, 0x44, 0x87, 0x94, 0x38, + 0x84, 0xa7, 0x9f, 0x5b, 0x9c, 0xae, 0x5f, 0x66, 0x08, 0xa2, 0xa0, 0x12, 0x22, 0xe9, 0xf9, 0xe9, + 0xf9, 0x10, 0x0d, 0x20, 0x16, 0x54, 0x54, 0x09, 0xbb, 0x03, 0x0a, 0x12, 0x8b, 0x12, 0x73, 0xa1, + 0x46, 0x2a, 0xed, 0x65, 0xe4, 0x92, 0xf3, 0x2d, 0x4e, 0x0f, 0x4e, 0x2d, 0xf1, 0x4d, 0x2c, 0xca, + 0x06, 0x91, 0x05, 0x05, 0xa9, 0x45, 0x41, 0xa9, 0x65, 0xa9, 0x79, 0xa5, 0xa9, 0xc1, 0x20, 0xf5, + 0x42, 0x66, 0x5c, 0x9c, 0x89, 0xa5, 0x25, 0x19, 0xf9, 0x45, 0x99, 0x25, 0x95, 0x12, 0x8c, 0x0a, + 0x8c, 0x1a, 0x9c, 0x4e, 0x12, 0x97, 0xb6, 0xe8, 0x8a, 0x40, 0x9d, 0xe6, 0x98, 0x92, 0x52, 0x94, + 0x5a, 0x5c, 0x1c, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x1e, 0x84, 0x50, 0x2a, 0x14, 0xcc, 0xc5, 0x06, + 0xb1, 0x4a, 0x82, 0x49, 0x81, 0x51, 0x83, 0xdb, 0xc8, 0x54, 0x0f, 0xab, 0x87, 0xf5, 0x70, 0x59, + 0x1c, 0x00, 0xd6, 0xec, 0xc4, 0x72, 0xe2, 0x9e, 0x3c, 0x43, 0x10, 0xd4, 0x28, 0x2b, 0xbe, 0xa6, + 0xe7, 0x1b, 0xb4, 0x10, 0x96, 0x28, 0x69, 0x70, 0xa9, 0xe1, 0x77, 0x7e, 0x50, 0x6a, 0x71, 0x41, + 0x7e, 0x5e, 0x71, 0xaa, 0xd1, 0x6c, 0x46, 0x2e, 0x66, 0xdf, 0xe2, 0x74, 0xa1, 0xc9, 0x8c, 0x5c, + 0xd2, 0xf8, 0xbc, 0x8b, 0xd3, 0x99, 0x78, 0xad, 0x91, 0xb2, 0x25, 0x4b, 0x1b, 0xcc, 0x75, 0x4e, + 0x21, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, + 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x95, 0x9e, 0x59, 0x92, + 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0x12, 0xa1, 0x65, 0x26, 0xba, 0xc9, 0x19, 0x89, + 0x99, 0x79, 0xfa, 0x70, 0x91, 0x0a, 0xa4, 0x54, 0x56, 0x59, 0x90, 0x5a, 0x9c, 0xc4, 0x06, 0x96, + 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x7d, 0xd8, 0xce, 0xb5, 0x8b, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // SetMarketMapperRevenueShare sets the revenue share for a market + // mapper. + SetMarketMapperRevenueShare(ctx context.Context, in *MsgSetMarketMapperRevenueShare, opts ...grpc.CallOption) (*MsgSetMarketMapperRevenueShareResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) SetMarketMapperRevenueShare(ctx context.Context, in *MsgSetMarketMapperRevenueShare, opts ...grpc.CallOption) (*MsgSetMarketMapperRevenueShareResponse, error) { + out := new(MsgSetMarketMapperRevenueShareResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.revshare.Msg/SetMarketMapperRevenueShare", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // SetMarketMapperRevenueShare sets the revenue share for a market + // mapper. + SetMarketMapperRevenueShare(context.Context, *MsgSetMarketMapperRevenueShare) (*MsgSetMarketMapperRevenueShareResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) SetMarketMapperRevenueShare(ctx context.Context, req *MsgSetMarketMapperRevenueShare) (*MsgSetMarketMapperRevenueShareResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetMarketMapperRevenueShare not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_SetMarketMapperRevenueShare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSetMarketMapperRevenueShare) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SetMarketMapperRevenueShare(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.revshare.Msg/SetMarketMapperRevenueShare", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SetMarketMapperRevenueShare(ctx, req.(*MsgSetMarketMapperRevenueShare)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "dydxprotocol.revshare.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SetMarketMapperRevenueShare", + Handler: _Msg_SetMarketMapperRevenueShare_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "dydxprotocol/revshare/tx.proto", +} + +func (m *MsgSetMarketMapperRevenueShare) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetMarketMapperRevenueShare) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetMarketMapperRevenueShare) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSetMarketMapperRevenueShareResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetMarketMapperRevenueShareResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetMarketMapperRevenueShareResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgSetMarketMapperRevenueShare) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgSetMarketMapperRevenueShareResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgSetMarketMapperRevenueShare) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetMarketMapperRevenueShare: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetMarketMapperRevenueShare: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSetMarketMapperRevenueShareResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetMarketMapperRevenueShareResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetMarketMapperRevenueShareResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/rewards/keeper/keeper.go b/protocol/x/rewards/keeper/keeper.go index 1fad0000ea..85627b804a 100644 --- a/protocol/x/rewards/keeper/keeper.go +++ b/protocol/x/rewards/keeper/keeper.go @@ -141,7 +141,7 @@ func (k Keeper) AddRewardSharesForFill( bigTakerFeeQuoteQuantums, makerRebateMulTakerVolume, ) - if takerWeight.Cmp(lib.BigInt0()) > 0 { + if takerWeight.Sign() > 0 { // We aren't concerned with errors here because we've already validated the weight is positive. if err := k.AddRewardShareToAddress( ctx, @@ -158,7 +158,7 @@ func (k Keeper) AddRewardSharesForFill( // Process reward weight for maker. makerWeight := new(big.Int).Set(bigMakerFeeQuoteQuantums) - if makerWeight.Cmp(lib.BigInt0()) > 0 { + if makerWeight.Sign() > 0 { // We aren't concerned with errors here because we've already validated the weight is positive. if err := k.AddRewardShareToAddress( ctx, @@ -182,7 +182,7 @@ func (k Keeper) AddRewardShareToAddress( address string, weight *big.Int, ) error { - if weight.Cmp(lib.BigInt0()) <= 0 { + if weight.Sign() <= 0 { return errorsmod.Wrapf( types.ErrNonpositiveWeight, "Invalid weight %v", diff --git a/protocol/x/sending/simulation/operations.go b/protocol/x/sending/simulation/operations.go index 2dd5456a66..96e585fa33 100644 --- a/protocol/x/sending/simulation/operations.go +++ b/protocol/x/sending/simulation/operations.go @@ -82,7 +82,7 @@ func SimulateMsgCreateTransfer( ), nil, nil } - bigNetCollateral, bigInitialMargin, _, err := sk.GetNetCollateralAndMarginRequirements( + risk, err := sk.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{ SubaccountId: *senderAccount.GetId(), @@ -111,7 +111,7 @@ func SimulateMsgCreateTransfer( ) // Calculate the maximum amount that can be sent without making the subaccount under-collateralized. - bigAmountPayable := new(big.Int).Sub(bigNetCollateral, bigInitialMargin) + bigAmountPayable := new(big.Int).Sub(risk.NC, risk.IMR) bigMaxAmountToSend := lib.BigMin(bigAmountPayable, bigAmountReceivable) if bigMaxAmountToSend.Sign() <= 0 { diff --git a/protocol/x/sending/types/expected_keepers.go b/protocol/x/sending/types/expected_keepers.go index 17678270e9..bbd7141af6 100644 --- a/protocol/x/sending/types/expected_keepers.go +++ b/protocol/x/sending/types/expected_keepers.go @@ -6,6 +6,7 @@ import ( "math/rand" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -16,9 +17,7 @@ type SubaccountsKeeper interface { ctx sdk.Context, update satypes.Update, ) ( - bigNetCollateral *big.Int, - bigInitialMargin *big.Int, - bigMaintenanceMargin *big.Int, + risk margin.Risk, err error, ) CanUpdateSubaccounts( diff --git a/protocol/x/subaccounts/keeper/isolated_subaccount.go b/protocol/x/subaccounts/keeper/isolated_subaccount.go index 4db1a60a42..af628f19fc 100644 --- a/protocol/x/subaccounts/keeper/isolated_subaccount.go +++ b/protocol/x/subaccounts/keeper/isolated_subaccount.go @@ -24,20 +24,16 @@ import ( func (k Keeper) checkIsolatedSubaccountConstraints( ctx sdk.Context, settledUpdates []types.SettledUpdate, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, ) ( success bool, successPerUpdate []types.UpdateResult, - err error, ) { success = true successPerUpdate = make([]types.UpdateResult, len(settledUpdates)) for i, u := range settledUpdates { - result, err := isValidIsolatedPerpetualUpdates(u, perpInfos) - if err != nil { - return false, nil, err - } + result := isValidIsolatedPerpetualUpdates(u, perpInfos) if result != types.Success { success = false } @@ -45,7 +41,7 @@ func (k Keeper) checkIsolatedSubaccountConstraints( successPerUpdate[i] = result } - return success, successPerUpdate, nil + return success, successPerUpdate } // Checks whether the perpetual updates to a settled subaccount violates constraints for isolated @@ -60,23 +56,19 @@ func (k Keeper) checkIsolatedSubaccountConstraints( // perpetuals or a combination of isolated and non-isolated perpetuals func isValidIsolatedPerpetualUpdates( settledUpdate types.SettledUpdate, - perpInfos map[uint32]perptypes.PerpInfo, -) (types.UpdateResult, error) { + perpInfos perptypes.PerpInfos, +) types.UpdateResult { // If there are no perpetual updates, then this update does not violate constraints for isolated // markets. if len(settledUpdate.PerpetualUpdates) == 0 { - return types.Success, nil + return types.Success } // Check if the updates contain an update to an isolated perpetual. hasIsolatedUpdate := false isolatedUpdatePerpetualId := uint32(math.MaxUint32) for _, perpetualUpdate := range settledUpdate.PerpetualUpdates { - perpInfo, exists := perpInfos[perpetualUpdate.PerpetualId] - if !exists { - return types.UpdateCausedError, - errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpetualUpdate.PerpetualId) - } + perpInfo := perpInfos.MustGet(perpetualUpdate.PerpetualId) if perpInfo.Perpetual.Params.MarketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { hasIsolatedUpdate = true @@ -91,11 +83,7 @@ func isValidIsolatedPerpetualUpdates( isolatedPositionPerpetualId := uint32(math.MaxUint32) hasPerpetualPositions := len(settledUpdate.SettledSubaccount.PerpetualPositions) > 0 for _, perpetualPosition := range settledUpdate.SettledSubaccount.PerpetualPositions { - perpInfo, exists := perpInfos[perpetualPosition.PerpetualId] - if !exists { - return types.UpdateCausedError, - errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpetualPosition.PerpetualId) - } + perpInfo := perpInfos.MustGet(perpetualPosition.PerpetualId) if perpInfo.Perpetual.Params.MarketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { isIsolatedSubaccount = true @@ -107,19 +95,19 @@ func isValidIsolatedPerpetualUpdates( // A subaccount with a perpetual position in an isolated perpetual cannot have updates to other // non-isolated perpetuals. if isIsolatedSubaccount && !hasIsolatedUpdate { - return types.ViolatesIsolatedSubaccountConstraints, nil + return types.ViolatesIsolatedSubaccountConstraints } // A subaccount with perpetual positions in non-isolated perpetuals cannot have an update // to an isolated perpetual. if !isIsolatedSubaccount && hasPerpetualPositions && hasIsolatedUpdate { - return types.ViolatesIsolatedSubaccountConstraints, nil + return types.ViolatesIsolatedSubaccountConstraints } // There cannot be more than a single perpetual update if an update to an isolated perpetual // exists in the slice of perpetual updates. if hasIsolatedUpdate && len(settledUpdate.PerpetualUpdates) > 1 { - return types.ViolatesIsolatedSubaccountConstraints, nil + return types.ViolatesIsolatedSubaccountConstraints } // Note we can assume that if `hasIsolatedUpdate` is true, there is only a single perpetual @@ -129,10 +117,10 @@ func isValidIsolatedPerpetualUpdates( if isIsolatedSubaccount && hasIsolatedUpdate && isolatedPositionPerpetualId != isolatedUpdatePerpetualId { - return types.ViolatesIsolatedSubaccountConstraints, nil + return types.ViolatesIsolatedSubaccountConstraints } - return types.Success, nil + return types.Success } // GetIsolatedPerpetualStateTransition computes whether an isolated perpetual position will be @@ -142,7 +130,7 @@ func isValidIsolatedPerpetualUpdates( // so all the updates must have been applied already to the subaccount. func GetIsolatedPerpetualStateTransition( settledUpdateWithUpdatedSubaccount types.SettledUpdate, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, ) (*types.IsolatedPerpetualPositionStateTransition, error) { // This subaccount needs to have had the updates in the `settledUpdate` already applied to it. updatedSubaccount := settledUpdateWithUpdatedSubaccount.SettledSubaccount @@ -163,10 +151,7 @@ func GetIsolatedPerpetualStateTransition( // Now, from the above checks, we know there is only a single perpetual update and 0 or 1 perpetual // positions. perpetualUpdate := settledUpdateWithUpdatedSubaccount.PerpetualUpdates[0] - perpInfo, exists := perpInfos[perpetualUpdate.PerpetualId] - if !exists { - return nil, errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpetualUpdate.PerpetualId) - } + perpInfo := perpInfos.MustGet(perpetualUpdate.PerpetualId) // If the perpetual update is not for an isolated perpetual, no isolated perpetual position is // being opened or closed. if perpInfo.Perpetual.Params.MarketType != perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { @@ -318,7 +303,7 @@ func (k *Keeper) transferCollateralForIsolatedPerpetual( func (k *Keeper) computeAndExecuteCollateralTransfer( ctx sdk.Context, settledUpdateWithUpdatedSubaccount types.SettledUpdate, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, ) error { // The subaccount in `settledUpdateWithUpdatedSubaccount` already has the perpetual updates // and asset updates applied to it. diff --git a/protocol/x/subaccounts/keeper/isolated_subaccount_test.go b/protocol/x/subaccounts/keeper/isolated_subaccount_test.go index b42046a52f..265efb8177 100644 --- a/protocol/x/subaccounts/keeper/isolated_subaccount_test.go +++ b/protocol/x/subaccounts/keeper/isolated_subaccount_test.go @@ -319,7 +319,7 @@ func TestGetIsolatedPerpetualStateTransition(t *testing.T) { for name, tc := range tests { t.Run( name, func(t *testing.T) { - perpInfos := make(map[uint32]perptypes.PerpInfo, len(tc.perpetuals)) + perpInfos := make(perptypes.PerpInfos, len(tc.perpetuals)) for _, perp := range tc.perpetuals { perpInfos[perp.Params.Id] = perptypes.PerpInfo{ Perpetual: perp, diff --git a/protocol/x/subaccounts/keeper/safety_heap_state.go b/protocol/x/subaccounts/keeper/safety_heap_state.go new file mode 100644 index 0000000000..54ca69ca6b --- /dev/null +++ b/protocol/x/subaccounts/keeper/safety_heap_state.go @@ -0,0 +1,172 @@ +package keeper + +import ( + "cosmossdk.io/store/prefix" + gogotypes "github.com/cosmos/gogoproto/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" +) + +// MustGetSubaccountAtIndex returns the subaccount at the given index. +// Panics if the subaccount is not found. +func (k Keeper) MustGetSubaccountAtIndex( + store prefix.Store, + heapIndex uint32, +) ( + subaccountId types.SubaccountId, +) { + subaccountId, found := k.GetSubaccountAtIndex(store, heapIndex) + if !found { + panic(types.ErrSafetyHeapSubaccountNotFoundAtIndex) + } + return subaccountId +} + +// GetSubaccountAtIndex returns the subaccount at the given index. +func (k Keeper) GetSubaccountAtIndex( + store prefix.Store, + heapIndex uint32, +) ( + subaccountId types.SubaccountId, + found bool, +) { + prefix := prefix.NewStore( + store, + []byte(types.SafetyHeapSubaccountIdsPrefix), + ) + key := lib.Uint32ToKey(heapIndex) + + b := prefix.Get(key) + + if b != nil { + k.cdc.MustUnmarshal(b, &subaccountId) + } + return subaccountId, b != nil +} + +// SetSubaccountAtIndex updates the subaccount at the given index. +func (k Keeper) SetSubaccountAtIndex( + store prefix.Store, + subaccountId types.SubaccountId, + heapIndex uint32, +) { + prefix := prefix.NewStore( + store, + []byte(types.SafetyHeapSubaccountIdsPrefix), + ) + key := lib.Uint32ToKey(heapIndex) + + prefix.Set( + key, + k.cdc.MustMarshal(&subaccountId), + ) + k.SetSubaccountHeapIndex(store, subaccountId, heapIndex) +} + +// DeleteSubaccountAtIndex deletes the subaccount at the given index. +// Panics if the heap index is not found. +func (k Keeper) DeleteSubaccountAtIndex( + store prefix.Store, + heapIndex uint32, +) { + prefix := prefix.NewStore( + store, + []byte(types.SafetyHeapSubaccountIdsPrefix), + ) + subaccountId := k.MustGetSubaccountAtIndex(store, heapIndex) + + key := lib.Uint32ToKey(heapIndex) + prefix.Delete(key) + + k.DeleteSubaccountHeapIndex(store, subaccountId) +} + +// MustGetSubaccountHeapIndex returns the heap index of the subaccount. +// Panics if the heap index is not found. +func (k Keeper) MustGetSubaccountHeapIndex( + store prefix.Store, + subaccountId types.SubaccountId, +) ( + heapIndex uint32, +) { + heapIndex, found := k.GetSubaccountHeapIndex(store, subaccountId) + if !found { + panic(types.ErrSafetyHeapSubaccountIndexNotFound) + } + return heapIndex +} + +// GetSubaccountHeapIndex returns the heap index of the subaccount. +func (k Keeper) GetSubaccountHeapIndex( + store prefix.Store, + subaccountId types.SubaccountId, +) ( + heapIndex uint32, + found bool, +) { + key := subaccountId.ToStateKey() + + index := gogotypes.UInt32Value{Value: 0} + b := store.Get(key) + + if b != nil { + k.cdc.MustUnmarshal(b, &index) + } + return index.Value, b != nil +} + +// SetSubaccountHeapIndex sets the heap index of the subaccount. +func (k Keeper) SetSubaccountHeapIndex( + store prefix.Store, + subaccountId types.SubaccountId, + heapIndex uint32, +) { + key := subaccountId.ToStateKey() + + index := gogotypes.UInt32Value{Value: heapIndex} + store.Set( + key, + k.cdc.MustMarshal(&index), + ) +} + +// DeleteSubaccountHeapIndex deletes the heap index of the subaccount. +func (k Keeper) DeleteSubaccountHeapIndex( + store prefix.Store, + subaccountId types.SubaccountId, +) { + key := subaccountId.ToStateKey() + store.Delete(key) +} + +// GetSafetyHeapLength returns the length of heap. +func (k Keeper) GetSafetyHeapLength( + store prefix.Store, +) ( + length uint32, +) { + key := []byte(types.SafetyHeapLengthPrefix) + + index := gogotypes.UInt32Value{Value: 0} + b := store.Get(key) + + if b != nil { + k.cdc.MustUnmarshal(b, &index) + } + + return index.Value +} + +// SetSafetyHeapLength sets the heap length. +func (k Keeper) SetSafetyHeapLength( + store prefix.Store, + length uint32, +) { + key := []byte(types.SafetyHeapLengthPrefix) + + index := gogotypes.UInt32Value{Value: length} + store.Set( + key, + k.cdc.MustMarshal(&index), + ) +} diff --git a/protocol/x/subaccounts/keeper/safety_heap_state_test.go b/protocol/x/subaccounts/keeper/safety_heap_state_test.go new file mode 100644 index 0000000000..0a45efd5b9 --- /dev/null +++ b/protocol/x/subaccounts/keeper/safety_heap_state_test.go @@ -0,0 +1,122 @@ +package keeper_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/stretchr/testify/require" +) + +func TestGetSetSubaccountAtIndex(t *testing.T) { + // Setup keeper state and test parameters. + ctx, subaccountsKeeper, _, _, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, false) + + // Write a couple of subaccounts to the store. + store := subaccountsKeeper.GetSafetyHeapStore(ctx, 0, types.Long) + subaccountsKeeper.SetSubaccountAtIndex(store, constants.Alice_Num0, 0) + subaccountsKeeper.SetSubaccountAtIndex(store, constants.Bob_Num0, 1) + subaccountsKeeper.SetSubaccountAtIndex(store, constants.Carl_Num0, 2) + + // Get. + require.Equal(t, constants.Alice_Num0, subaccountsKeeper.MustGetSubaccountAtIndex(store, 0)) + require.Equal(t, constants.Bob_Num0, subaccountsKeeper.MustGetSubaccountAtIndex(store, 1)) + require.Equal(t, constants.Carl_Num0, subaccountsKeeper.MustGetSubaccountAtIndex(store, 2)) + + // Overwrite the subaccount at index 1. + subaccountsKeeper.SetSubaccountAtIndex(store, constants.Alice_Num1, 0) + require.Equal(t, constants.Alice_Num1, subaccountsKeeper.MustGetSubaccountAtIndex(store, 0)) + subaccountsKeeper.SetSubaccountAtIndex(store, constants.Dave_Num0, 1) + require.Equal(t, constants.Dave_Num0, subaccountsKeeper.MustGetSubaccountAtIndex(store, 1)) + + // Delete + subaccountsKeeper.DeleteSubaccountAtIndex(store, 2) + subaccountsKeeper.DeleteSubaccountAtIndex(store, 1) + + // Getting non existent subaccount. + _, found := subaccountsKeeper.GetSubaccountAtIndex(store, 1) + require.False(t, found) + + require.PanicsWithError( + t, + types.ErrSafetyHeapSubaccountNotFoundAtIndex.Error(), + func() { + subaccountsKeeper.MustGetSubaccountAtIndex(store, 1) + }, + ) + + _, found = subaccountsKeeper.GetSubaccountAtIndex(store, 2) + require.False(t, found) + + require.PanicsWithError( + t, + types.ErrSafetyHeapSubaccountNotFoundAtIndex.Error(), + func() { + subaccountsKeeper.MustGetSubaccountAtIndex(store, 2) + }, + ) +} + +func TestGetSetSubaccountHeapIndex(t *testing.T) { + // Setup keeper state and test parameters. + ctx, subaccountsKeeper, _, _, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, false) + + // Write a couple of subaccounts to the store. + store := subaccountsKeeper.GetSafetyHeapStore(ctx, 0, types.Long) + subaccountsKeeper.SetSubaccountHeapIndex(store, constants.Alice_Num0, 0) + subaccountsKeeper.SetSubaccountHeapIndex(store, constants.Bob_Num0, 1) + subaccountsKeeper.SetSubaccountHeapIndex(store, constants.Carl_Num0, 2) + + // Get. + require.Equal(t, uint32(0), subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Alice_Num0)) + require.Equal(t, uint32(1), subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Bob_Num0)) + require.Equal(t, uint32(2), subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Carl_Num0)) + + // Overwrite the subaccount at index 1. + subaccountsKeeper.SetSubaccountHeapIndex(store, constants.Alice_Num0, 3) + require.Equal(t, uint32(3), subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Alice_Num0)) + subaccountsKeeper.SetSubaccountHeapIndex(store, constants.Carl_Num0, 4) + require.Equal(t, uint32(4), subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Carl_Num0)) + + // Delete + subaccountsKeeper.DeleteSubaccountHeapIndex(store, constants.Alice_Num0) + subaccountsKeeper.DeleteSubaccountHeapIndex(store, constants.Carl_Num0) + + // Getting non existent subaccount. + _, found := subaccountsKeeper.GetSubaccountHeapIndex(store, constants.Alice_Num0) + require.False(t, found) + + require.PanicsWithError( + t, + types.ErrSafetyHeapSubaccountIndexNotFound.Error(), + func() { + subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Alice_Num0) + }, + ) + + _, found = subaccountsKeeper.GetSubaccountHeapIndex(store, constants.Carl_Num0) + require.False(t, found) + + require.PanicsWithError( + t, + types.ErrSafetyHeapSubaccountIndexNotFound.Error(), + func() { + subaccountsKeeper.MustGetSubaccountHeapIndex(store, constants.Carl_Num0) + }, + ) +} + +func TestGetSetSafetyHeapLength(t *testing.T) { + // Setup keeper state and test parameters. + ctx, subaccountsKeeper, _, _, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, false) + + // Write a couple of subaccounts to the store. + store := subaccountsKeeper.GetSafetyHeapStore(ctx, 0, types.Long) + + require.Equal(t, uint32(0), subaccountsKeeper.GetSafetyHeapLength(store)) + + subaccountsKeeper.SetSafetyHeapLength(store, 7) + + require.Equal(t, uint32(7), subaccountsKeeper.GetSafetyHeapLength(store)) +} diff --git a/protocol/x/subaccounts/keeper/safety_heap_stores.go b/protocol/x/subaccounts/keeper/safety_heap_stores.go new file mode 100644 index 0000000000..8c4d4ec351 --- /dev/null +++ b/protocol/x/subaccounts/keeper/safety_heap_stores.go @@ -0,0 +1,36 @@ +package keeper + +import ( + "fmt" + + "cosmossdk.io/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" +) + +// GetSafetyHeapStore returns the safety heap store. +func (k Keeper) GetSafetyHeapStore( + ctx sdk.Context, + perpetualId uint32, + side types.SafetyHeapPositionSide, +) prefix.Store { + return prefix.NewStore( + ctx.KVStore(k.storeKey), + k.GetSafetyHeapKeyPrefix(perpetualId, side), + ) +} + +// GetSafetyHeapKeyPrefix returns the prefix for the safety heap store. +func (k Keeper) GetSafetyHeapKeyPrefix( + perpetualId uint32, + side types.SafetyHeapPositionSide, +) []byte { + return []byte( + fmt.Sprintf( + "%s/%d/%d/", + types.SafetyHeapStorePrefix, + perpetualId, + side, + ), + ) +} diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index f89096d8af..a67c89d9e6 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -15,15 +15,15 @@ import ( errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/prefix" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/dtypes" indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" indexer_manager "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" - perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + salib "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/lib" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" gometrics "github.com/hashicorp/go-metrics" ) @@ -211,7 +211,7 @@ func (k Keeper) getRandomBytes(ctx sdk.Context, rand *rand.Rand) ([]byte, error) func (k Keeper) getSettledUpdates( ctx sdk.Context, updates []types.Update, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, requireUniqueSubaccount bool, ) ( settledUpdates []types.SettledUpdate, @@ -235,10 +235,7 @@ func (k Keeper) getSettledUpdates( // idToSettledSubaccount map. if !exists { subaccount := k.GetSubaccount(ctx, u.SubaccountId) - settledSubaccount, fundingPayments, err = GetSettledSubaccountWithPerpetuals(subaccount, perpInfos) - if err != nil { - return nil, nil, err - } + settledSubaccount, fundingPayments = salib.GetSettledSubaccountWithPerpetuals(subaccount, perpInfos) idToSettledSubaccount[u.SubaccountId] = settledSubaccount subaccountIdToFundingPayments[u.SubaccountId] = fundingPayments @@ -308,7 +305,7 @@ func (k Keeper) UpdateSubaccounts( } // Get OpenInterestDelta from the updates, and persist the OI change if any. - perpOpenInterestDelta := GetDeltaOpenInterestFromUpdates(settledUpdates, updateType) + perpOpenInterestDelta := salib.GetDeltaOpenInterestFromUpdates(settledUpdates, updateType) if perpOpenInterestDelta != nil { if err := k.perpetualsKeeper.ModifyOpenInterest( ctx, @@ -327,13 +324,13 @@ func (k Keeper) UpdateSubaccounts( } // Apply the updates to perpetual positions. - UpdatePerpetualPositions( + salib.UpdatePerpetualPositions( settledUpdates, perpInfos, ) // Apply the updates to asset positions. - UpdateAssetPositions(settledUpdates) + salib.UpdateAssetPositions(settledUpdates) // Transfer collateral between collateral pools for any isolated perpetual positions that changed // state due to an update. @@ -363,11 +360,11 @@ func (k Keeper) UpdateSubaccounts( indexer_manager.GetBytes( indexerevents.NewSubaccountUpdateEvent( u.SettledSubaccount.Id, - getUpdatedPerpetualPositions( + salib.GetUpdatedPerpetualPositions( u, fundingPayments, ), - getUpdatedAssetPositions(u), + salib.GetUpdatedAssetPositions(u), fundingPayments, ), ), @@ -435,79 +432,6 @@ func (k Keeper) CanUpdateSubaccounts( return success, successPerUpdate, err } -// GetSettledSubaccountWithPerpetuals returns 1. a new settled subaccount given an unsettled subaccount, -// updating the USDC AssetPosition, FundingIndex, and LastFundingPayment fields accordingly -// (does not persist any changes) and 2. a map with perpetual ID as key and last funding -// payment as value (for emitting funding payments to indexer). -// -// Note that this is a stateless utility function. -func GetSettledSubaccountWithPerpetuals( - subaccount types.Subaccount, - perpInfos map[uint32]perptypes.PerpInfo, -) ( - settledSubaccount types.Subaccount, - fundingPayments map[uint32]dtypes.SerializableInt, - err error, -) { - totalNetSettlementPpm := big.NewInt(0) - - newPerpetualPositions := []*types.PerpetualPosition{} - fundingPayments = make(map[uint32]dtypes.SerializableInt) - - // Iterate through and settle all perpetual positions. - for _, p := range subaccount.PerpetualPositions { - perpInfo, found := perpInfos[p.PerpetualId] - if !found { - return types.Subaccount{}, nil, errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", p.PerpetualId) - } - - // Call the stateless utility function to get the net settlement and new funding index. - bigNetSettlementPpm, newFundingIndex := perplib.GetSettlementPpmWithPerpetual( - perpInfo.Perpetual, - p.GetBigQuantums(), - p.FundingIndex.BigInt(), - ) - // Record non-zero funding payment (to be later emitted in SubaccountUpdateEvent to indexer). - // Note: Funding payment is the negative of settlement, i.e. positive settlement is equivalent - // to a negative funding payment (position received funding payment) and vice versa. - if bigNetSettlementPpm.Cmp(lib.BigInt0()) != 0 { - fundingPayments[p.PerpetualId] = dtypes.NewIntFromBigInt( - new(big.Int).Neg( - new(big.Int).Div(bigNetSettlementPpm, lib.BigIntOneMillion()), - ), - ) - } - - // Aggregate all net settlements. - totalNetSettlementPpm.Add(totalNetSettlementPpm, bigNetSettlementPpm) - - // Update cached funding index of the perpetual position. - newPerpetualPositions = append( - newPerpetualPositions, &types.PerpetualPosition{ - PerpetualId: p.PerpetualId, - Quantums: p.Quantums, - FundingIndex: dtypes.NewIntFromBigInt(newFundingIndex), - }, - ) - } - - newSubaccount := types.Subaccount{ - Id: subaccount.Id, - AssetPositions: subaccount.AssetPositions, - PerpetualPositions: newPerpetualPositions, - MarginEnabled: subaccount.MarginEnabled, - } - newUsdcPosition := new(big.Int).Add( - subaccount.GetUsdcPosition(), - // `Div` implements Euclidean division (unlike Go). When the diviser is positive, - // division result always rounds towards negative infinity. - totalNetSettlementPpm.Div(totalNetSettlementPpm, lib.BigIntOneMillion()), - ) - // TODO(CLOB-993): Remove this function and use `UpdateAssetPositions` instead. - newSubaccount.SetUsdcAssetPosition(newUsdcPosition) - return newSubaccount, fundingPayments, nil -} - func checkPositionUpdatable( ctx sdk.Context, pk types.ProductKeeper, @@ -550,7 +474,7 @@ func (k Keeper) internalCanUpdateSubaccounts( ctx sdk.Context, settledUpdates []types.SettledUpdate, updateType types.UpdateType, - perpInfos map[uint32]perptypes.PerpInfo, + perpInfos perptypes.PerpInfos, ) ( success bool, successPerUpdate []types.UpdateResult, @@ -559,14 +483,11 @@ func (k Keeper) internalCanUpdateSubaccounts( // TODO(TRA-99): Add integration / E2E tests on order placement / matching with this new // constraint. // Check if the updates satisfy the isolated perpetual constraints. - success, successPerUpdate, err = k.checkIsolatedSubaccountConstraints( + success, successPerUpdate = k.checkIsolatedSubaccountConstraints( ctx, settledUpdates, perpInfos, ) - if err != nil { - return false, nil, err - } if !success { return success, successPerUpdate, nil } @@ -638,15 +559,12 @@ func (k Keeper) internalCanUpdateSubaccounts( // Get delta open interest from the updates. // `perpOpenInterestDelta` is nil if the update type is not `Match` or if the updates // do not result in OI changes. - perpOpenInterestDelta := GetDeltaOpenInterestFromUpdates(settledUpdates, updateType) + perpOpenInterestDelta := salib.GetDeltaOpenInterestFromUpdates(settledUpdates, updateType) // Temporily apply open interest delta to perpetuals, so IMF is calculated based on open interest after the update. // `perpOpenInterestDeltas` is only present for `Match` update type. if perpOpenInterestDelta != nil { - perpInfo, ok := perpInfos[perpOpenInterestDelta.PerpetualId] - if !ok { - return false, nil, errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", perpOpenInterestDelta.PerpetualId) - } + perpInfo := perpInfos.MustGet(perpOpenInterestDelta.PerpetualId) existingValue := big.NewInt(0) if !perpInfo.Perpetual.OpenInterest.IsNil() { existingValue.Set(perpInfo.Perpetual.OpenInterest.BigInt()) @@ -663,9 +581,7 @@ func (k Keeper) internalCanUpdateSubaccounts( }() } - bigCurNetCollateral := make(map[string]*big.Int) - bigCurInitialMargin := make(map[string]*big.Int) - bigCurMaintenanceMargin := make(map[string]*big.Int) + riskCurMap := make(map[string]margin.Risk) // Iterate over all updates. for i, u := range settledUpdates { @@ -686,16 +602,10 @@ func (k Keeper) internalCanUpdateSubaccounts( } // Get the new collateralization and margin requirements with the update applied. - bigNewNetCollateral, - bigNewInitialMargin, - bigNewMaintenanceMargin, - err := k.internalGetNetCollateralAndMarginRequirements( - ctx, + riskNew, err := salib.GetRiskForSubaccount( u, perpInfos, ) - - // if `internalGetNetCollateralAndMarginRequirements`, returns error. if err != nil { return false, nil, err } @@ -704,7 +614,7 @@ func (k Keeper) internalCanUpdateSubaccounts( // The subaccount is not well-collateralized after the update. // We must now check if the state transition is valid. - if bigNewInitialMargin.Cmp(bigNewNetCollateral) > 0 { + if !riskNew.IsInitialCollateralized() { // Get the current collateralization and margin requirements without the update applied. emptyUpdate := types.SettledUpdate{ SettledSubaccount: u.SettledSubaccount, @@ -717,12 +627,8 @@ func (k Keeper) internalCanUpdateSubaccounts( saKey := string(bytes) // Cache the current collateralization and margin requirements for the subaccount. - if _, ok := bigCurNetCollateral[saKey]; !ok { - bigCurNetCollateral[saKey], - bigCurInitialMargin[saKey], - bigCurMaintenanceMargin[saKey], - err = k.internalGetNetCollateralAndMarginRequirements( - ctx, + if _, ok := riskCurMap[saKey]; !ok { + riskCurMap[saKey], err = salib.GetRiskForSubaccount( emptyUpdate, perpInfos, ) @@ -732,12 +638,9 @@ func (k Keeper) internalCanUpdateSubaccounts( } // Determine whether the state transition is valid. - result = IsValidStateTransitionForUndercollateralizedSubaccount( - bigCurNetCollateral[saKey], - bigCurInitialMargin[saKey], - bigCurMaintenanceMargin[saKey], - bigNewNetCollateral, - bigNewMaintenanceMargin, + result = salib.IsValidStateTransitionForUndercollateralizedSubaccount( + riskCurMap[saKey], + riskNew, ) } @@ -752,74 +655,6 @@ func (k Keeper) internalCanUpdateSubaccounts( return success, successPerUpdate, nil } -// IsValidStateTransitionForUndercollateralizedSubaccount returns an `UpdateResult` -// denoting whether this state transition is valid. This function accepts the collateral and -// margin requirements of a subaccount before and after an update ("cur" and -// "new", respectively). -// -// This function should only be called if the account is undercollateralized after the update. -// -// A state transition is valid if the subaccount enters a -// "less-or-equally-risky" state after an update. -// i.e.`newNetCollateral / newMaintenanceMargin >= curNetCollateral / curMaintenanceMargin`. -// -// Otherwise, the state transition is invalid. If the account was previously undercollateralized, -// `types.StillUndercollateralized` is returned. If the account was previously -// collateralized and is now undercollateralized, `types.NewlyUndercollateralized` is -// returned. -// -// Note that the inequality `newNetCollateral / newMaintenanceMargin >= curNetCollateral / curMaintenanceMargin` -// has divide-by-zero issue when margin requirements are zero. To make sure the state -// transition is valid, we special case this scenario and only allow state transition that improves net collateral. -func IsValidStateTransitionForUndercollateralizedSubaccount( - bigCurNetCollateral *big.Int, - bigCurInitialMargin *big.Int, - bigCurMaintenanceMargin *big.Int, - bigNewNetCollateral *big.Int, - bigNewMaintenanceMargin *big.Int, -) types.UpdateResult { - // Determine whether the subaccount was previously undercollateralized before the update. - var underCollateralizationResult = types.StillUndercollateralized - if bigCurInitialMargin.Cmp(bigCurNetCollateral) <= 0 { - underCollateralizationResult = types.NewlyUndercollateralized - } - - // If the maintenance margin is increasing, then the subaccount is undercollateralized. - if bigNewMaintenanceMargin.Cmp(bigCurMaintenanceMargin) > 0 { - return underCollateralizationResult - } - - // If the maintenance margin is zero, it means the subaccount must have no open positions, and negative net - // collateral. If the net collateral is not improving then this transition is not valid. - if bigNewMaintenanceMargin.BitLen() == 0 || bigCurMaintenanceMargin.BitLen() == 0 { - if bigNewMaintenanceMargin.BitLen() == 0 && - bigCurMaintenanceMargin.BitLen() == 0 && - bigNewNetCollateral.Cmp(bigCurNetCollateral) > 0 { - return types.Success - } - - return underCollateralizationResult - } - - // Note that here we are effectively checking that - // `newNetCollateral / newMaintenanceMargin >= curNetCollateral / curMaintenanceMargin`. - // However, to avoid rounding errors, we factor this as - // `newNetCollateral * curMaintenanceMargin >= curNetCollateral * newMaintenanceMargin`. - bigCurRisk := new(big.Int).Mul(bigNewNetCollateral, bigCurMaintenanceMargin) - bigNewRisk := new(big.Int).Mul(bigCurNetCollateral, bigNewMaintenanceMargin) - - // The subaccount is not well-collateralized, and the state transition leaves the subaccount in a - // "more-risky" state (collateral relative to margin requirements is decreasing). - if bigNewRisk.Cmp(bigCurRisk) > 0 { - return underCollateralizationResult - } - - // The subaccount is in a "less-or-equally-risky" state (margin requirements are decreasing or unchanged, - // collateral relative to margin requirements is decreasing or unchanged). - // This subaccount is undercollateralized in this state, but we still consider this state transition valid. - return types.Success -} - // GetNetCollateralAndMarginRequirements returns the total net collateral, total initial margin requirement, // and total maintenance margin requirement for the subaccount as if the `update` was applied. // It is used to get information about speculative changes to the subaccount. @@ -834,21 +669,16 @@ func (k Keeper) GetNetCollateralAndMarginRequirements( ctx sdk.Context, update types.Update, ) ( - bigNetCollateral *big.Int, - bigInitialMargin *big.Int, - bigMaintenanceMargin *big.Int, + risk margin.Risk, err error, ) { subaccount := k.GetSubaccount(ctx, update.SubaccountId) perpInfos, err := k.GetAllRelevantPerpetuals(ctx, []types.Update{update}) if err != nil { - return nil, nil, nil, err - } - settledSubaccount, _, err := GetSettledSubaccountWithPerpetuals(subaccount, perpInfos) - if err != nil { - return nil, nil, nil, err + return risk, err } + settledSubaccount, _ := salib.GetSettledSubaccountWithPerpetuals(subaccount, perpInfos) settledUpdate := types.SettledUpdate{ SettledSubaccount: settledSubaccount, @@ -856,167 +686,12 @@ func (k Keeper) GetNetCollateralAndMarginRequirements( PerpetualUpdates: update.PerpetualUpdates, } - return k.internalGetNetCollateralAndMarginRequirements( - ctx, + return salib.GetRiskForSubaccount( settledUpdate, perpInfos, ) } -// internalGetNetCollateralAndMarginRequirements returns the total net collateral, total initial margin -// requirement, and total maintenance margin requirement for the `Subaccount` as if unsettled funding -// of existing positions were settled, and the `bigQuoteBalanceDeltaQuantums`, `assetUpdates`, and -// `perpetualUpdates` were applied. It is used to get information about speculative changes to the -// `Subaccount`. -// The input subaccounts must be settled. -// -// The provided update can also be "zeroed" in order to get information about -// the current state of the subaccount (i.e. with no changes). -// -// If two position updates reference the same position, an error is returned. -func (k Keeper) internalGetNetCollateralAndMarginRequirements( - ctx sdk.Context, - settledUpdate types.SettledUpdate, - perpInfos map[uint32]perptypes.PerpInfo, -) ( - bigNetCollateral *big.Int, - bigInitialMargin *big.Int, - bigMaintenanceMargin *big.Int, - err error, -) { - defer telemetry.ModuleMeasureSince( - types.ModuleName, - time.Now(), - metrics.GetNetCollateralAndMarginRequirements, - metrics.Latency, - ) - - // Initialize return values. - bigNetCollateral = big.NewInt(0) - bigInitialMargin = big.NewInt(0) - bigMaintenanceMargin = big.NewInt(0) - - // Merge updates and assets. - assetSizes, err := applyUpdatesToPositions( - settledUpdate.SettledSubaccount.AssetPositions, - settledUpdate.AssetUpdates, - ) - if err != nil { - return big.NewInt(0), big.NewInt(0), big.NewInt(0), err - } - - // Merge updates and perpetuals. - perpetualSizes, err := applyUpdatesToPositions( - settledUpdate.SettledSubaccount.PerpetualPositions, - settledUpdate.PerpetualUpdates, - ) - if err != nil { - return big.NewInt(0), big.NewInt(0), big.NewInt(0), err - } - - // Iterate over all assets and updates and calculate change to net collateral and margin requirements. - for _, size := range assetSizes { - id := size.GetId() - bigQuantums := size.GetBigQuantums() - - nc, err := k.assetsKeeper.GetNetCollateral(ctx, id, bigQuantums) - if err != nil { - return big.NewInt(0), big.NewInt(0), big.NewInt(0), err - } - - imr, mmr, err := k.assetsKeeper.GetMarginRequirements( - ctx, - id, - bigQuantums, - ) - if err != nil { - return big.NewInt(0), big.NewInt(0), big.NewInt(0), err - } - bigNetCollateral.Add(bigNetCollateral, nc) - bigInitialMargin.Add(bigInitialMargin, imr) - bigMaintenanceMargin.Add(bigMaintenanceMargin, mmr) - } - - // Iterate over all perpetuals and updates and calculate change to net collateral and margin requirements. - for _, size := range perpetualSizes { - perpInfo, found := perpInfos[size.GetId()] - if !found { - return big.NewInt(0), big.NewInt(0), big.NewInt(0), - errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", size.GetId()) - } - nc, imr, mmr := perplib.GetNetCollateralAndMarginRequirements( - perpInfo.Perpetual, - perpInfo.Price, - perpInfo.LiquidityTier, - size.GetBigQuantums(), - ) - bigNetCollateral.Add(bigNetCollateral, nc) - bigInitialMargin.Add(bigInitialMargin, imr) - bigMaintenanceMargin.Add(bigMaintenanceMargin, mmr) - } - - return bigNetCollateral, bigInitialMargin, bigMaintenanceMargin, nil -} - -// applyUpdatesToPositions merges a slice of `types.UpdatablePositions` and `types.PositionSize` -// (i.e. concrete types *types.AssetPosition` and `types.AssetUpdate`) into a slice of `types.PositionSize`. -// If a given `PositionSize` shares an ID with an `UpdatablePositionSize`, the update and position are merged -// into a single `PositionSize`. -// -// An error is returned if two updates share the same position id. -// -// Note: There are probably performance implications here for allocating a new slice of PositionSize, -// and for allocating new slices when converting the concrete types to interfaces. However, without doing -// this there would be a lot of duplicate code for calculating changes for both `Assets` and `Perpetuals`. -func applyUpdatesToPositions[ - P types.PositionSize, - U types.PositionSize, -](positions []P, updates []U) ([]types.PositionSize, error) { - var result []types.PositionSize = make([]types.PositionSize, 0, len(positions)+len(updates)) - - updateMap := make(map[uint32]types.PositionSize, len(updates)) - updateIndexMap := make(map[uint32]int, len(updates)) - for i, update := range updates { - // Check for non-unique updates (two updates to the same position). - id := update.GetId() - _, exists := updateMap[id] - if exists { - errMsg := fmt.Sprintf("Multiple updates exist for position %v", update.GetId()) - return nil, errorsmod.Wrap(types.ErrNonUniqueUpdatesPosition, errMsg) - } - - updateMap[id] = update - updateIndexMap[id] = i - result = append(result, update) - } - - // Iterate over each position, if the position shares an ID with - // an update, then we "merge" the update and the position into a new `PositionUpdate`. - for _, pos := range positions { - id := pos.GetId() - update, exists := updateMap[id] - if !exists { - result = append(result, pos) - } else { - var newPos = types.NewPositionUpdate(id) - - // Add the position size and update together to get the new size. - var bigNewPositionSize = new(big.Int).Add( - pos.GetBigQuantums(), - update.GetBigQuantums(), - ) - - newPos.SetBigQuantums(bigNewPositionSize) - - // Replace update with `PositionUpdate` - index := updateIndexMap[id] - result[index] = newPos - } - } - - return result, nil -} - // GetAllRelevantPerpetuals returns all relevant perpetual information for a given set of updates. // This includes all perpetuals that exist on the accounts already and all perpetuals that are // being updated in the input updates. @@ -1024,7 +699,7 @@ func (k Keeper) GetAllRelevantPerpetuals( ctx sdk.Context, updates []types.Update, ) ( - map[uint32]perptypes.PerpInfo, + perptypes.PerpInfos, error, ) { subaccountIds := make(map[types.SubaccountId]struct{}) @@ -1048,21 +723,30 @@ func (k Keeper) GetAllRelevantPerpetuals( } // Get all perpetual information from state. - perpetuals := make(map[uint32]perptypes.PerpInfo, len(perpIds)) + ltCache := make(map[uint32]perptypes.LiquidityTier) + perpInfos := make(perptypes.PerpInfos, len(perpIds)) for perpId := range perpIds { - perpetual, - price, - liquidityTier, - err := k.perpetualsKeeper.GetPerpetualAndMarketPriceAndLiquidityTier(ctx, perpId) + perpetual, price, err := k.perpetualsKeeper.GetPerpetualAndMarketPrice(ctx, perpId) if err != nil { return nil, err } - perpetuals[perpId] = perptypes.PerpInfo{ + + ltId := perpetual.Params.LiquidityTier + if _, ok := ltCache[ltId]; !ok { + liquidityTierFromState, err := k.perpetualsKeeper.GetLiquidityTier(ctx, ltId) + if err != nil { + return nil, err + } + ltCache[ltId] = liquidityTierFromState + } + liquidityTier := ltCache[ltId] + + perpInfos[perpId] = perptypes.PerpInfo{ Perpetual: perpetual, Price: price, LiquidityTier: liquidityTier, } } - return perpetuals, nil + return perpInfos, nil } diff --git a/protocol/x/subaccounts/keeper/subaccount_helper.go b/protocol/x/subaccounts/keeper/subaccount_helper.go deleted file mode 100644 index 01b23f19bc..0000000000 --- a/protocol/x/subaccounts/keeper/subaccount_helper.go +++ /dev/null @@ -1,224 +0,0 @@ -package keeper - -import ( - "sort" - - errorsmod "cosmossdk.io/errors" - - "github.com/dydxprotocol/v4-chain/protocol/dtypes" - perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" - "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" -) - -// getUpdatedAssetPositions filters out all the asset positions on a subaccount that have -// been updated. This will include any asset postions that were closed due to an update. -// TODO(DEC-1295): look into reducing code duplication here using Generics+Reflect. -func getUpdatedAssetPositions( - update types.SettledUpdate, -) []*types.AssetPosition { - assetIdToPositionMap := make(map[uint32]*types.AssetPosition) - for _, assetPosition := range update.SettledSubaccount.AssetPositions { - assetIdToPositionMap[assetPosition.AssetId] = assetPosition - } - - updatedAssetIds := make(map[uint32]struct{}) - for _, assetUpdate := range update.AssetUpdates { - updatedAssetIds[assetUpdate.AssetId] = struct{}{} - } - - updatedAssetPositions := make([]*types.AssetPosition, 0, len(updatedAssetIds)) - for updatedId := range updatedAssetIds { - assetPosition, exists := assetIdToPositionMap[updatedId] - // If a position does not exist on the subaccount with the asset id of an update, it must - // have been deleted due to quantums becoming 0. This needs to be included in the event, so we - // construct a position with the AssetId of the update and a Quantums value of 0. The other - // properties are left as the default values as a 0-sized position indicates the position is - // closed. - if !exists { - assetPosition = &types.AssetPosition{ - AssetId: updatedId, - Quantums: dtypes.ZeroInt(), - } - } - updatedAssetPositions = append(updatedAssetPositions, assetPosition) - } - - // Sort the asset positions in ascending order by asset id. - sort.Slice(updatedAssetPositions, func(i, j int) bool { - return updatedAssetPositions[i].GetId() < updatedAssetPositions[j].GetId() - }) - - return updatedAssetPositions -} - -// getUpdatedPerpetualPositions filters out all the perpetual positions on a subaccount that have -// been updated. This will include any perpetual postions that were closed due to an update or that -// received / paid out funding payments.. -func getUpdatedPerpetualPositions( - update types.SettledUpdate, - fundingPayments map[uint32]dtypes.SerializableInt, -) []*types.PerpetualPosition { - perpetualIdToPositionMap := make(map[uint32]*types.PerpetualPosition) - for _, perpetualPosition := range update.SettledSubaccount.PerpetualPositions { - perpetualIdToPositionMap[perpetualPosition.PerpetualId] = perpetualPosition - } - - // `updatedPerpetualIds` indicates which perpetuals were either explicitly updated - // (through update.PerpetualUpdates) or implicitly updated (had non-zero last funding - // payment). - updatedPerpetualIds := make(map[uint32]struct{}) - for _, perpetualUpdate := range update.PerpetualUpdates { - updatedPerpetualIds[perpetualUpdate.PerpetualId] = struct{}{} - } - // Mark perpetuals with non-zero funding payment also as updated. - for perpetualIdWithNonZeroLastFunding := range fundingPayments { - updatedPerpetualIds[perpetualIdWithNonZeroLastFunding] = struct{}{} - } - - updatedPerpetualPositions := make([]*types.PerpetualPosition, 0, len(updatedPerpetualIds)) - for updatedId := range updatedPerpetualIds { - perpetualPosition, exists := perpetualIdToPositionMap[updatedId] - // If a position does not exist on the subaccount with the perpetual id of an update, it must - // have been deleted due to quantums becoming 0. This needs to be included in the event, so we - // construct a position with the PerpetualId of the update and a Quantums value of 0. The other - // properties are left as the default values as a 0-sized position indicates the position is - // closed and thus the funding index and the side of the position does not matter. - if !exists { - perpetualPosition = &types.PerpetualPosition{ - PerpetualId: updatedId, - Quantums: dtypes.ZeroInt(), - } - } - updatedPerpetualPositions = append(updatedPerpetualPositions, perpetualPosition) - } - - // Sort the perpetual positions in ascending order by perpetual id. - sort.Slice(updatedPerpetualPositions, func(i, j int) bool { - return updatedPerpetualPositions[i].GetId() < updatedPerpetualPositions[j].GetId() - }) - - return updatedPerpetualPositions -} - -// For each settledUpdate in settledUpdates, updates its SettledSubaccount.PerpetualPositions -// to reflect settledUpdate.PerpetualUpdates. -// For newly created positions, use `perpIdToFundingIndex` map to populate the `FundingIndex` field. -func UpdatePerpetualPositions( - settledUpdates []types.SettledUpdate, - perpInfos map[uint32]perptypes.PerpInfo, -) { - // Apply the updates. - for i, u := range settledUpdates { - // Build a map of all the Subaccount's Perpetual Positions by id. - perpetualPositionsMap := make(map[uint32]*types.PerpetualPosition) - for _, pp := range u.SettledSubaccount.PerpetualPositions { - perpetualPositionsMap[pp.PerpetualId] = pp - } - - // Update the perpetual positions. - for _, pu := range u.PerpetualUpdates { - // Check if the `Subaccount` already has a position with the same id. - // If so – we update the size of the existing position, otherwise - // we create a new position. - if pp, exists := perpetualPositionsMap[pu.PerpetualId]; exists { - curQuantums := pp.GetBigQuantums() - updateQuantums := pu.GetBigQuantums() - newQuantums := curQuantums.Add(curQuantums, updateQuantums) - - // Handle the case where the position is now closed. - if newQuantums.Sign() == 0 { - delete(perpetualPositionsMap, pu.PerpetualId) - } - pp.Quantums = dtypes.NewIntFromBigInt(newQuantums) - } else { - // This subaccount does not have a matching position for this update. - // Create the new position. - perpInfo, exists := perpInfos[pu.PerpetualId] - if !exists { - // Invariant: `perpInfos` should all relevant perpetuals, which includes all - // perpetuals that are updated. - panic(errorsmod.Wrapf(types.ErrPerpetualInfoDoesNotExist, "%d", pu.PerpetualId)) - } - perpetualPosition := &types.PerpetualPosition{ - PerpetualId: pu.PerpetualId, - Quantums: dtypes.NewIntFromBigInt(pu.GetBigQuantums()), - FundingIndex: perpInfo.Perpetual.FundingIndex, - } - - // Add the new position to the map. - perpetualPositionsMap[pu.PerpetualId] = perpetualPosition - } - } - - // Convert the new PerpetualPostiion values back into a slice. - perpetualPositions := make([]*types.PerpetualPosition, 0, len(perpetualPositionsMap)) - for _, value := range perpetualPositionsMap { - perpetualPositions = append(perpetualPositions, value) - } - - // Sort the new PerpetualPositions in ascending order by Id. - sort.Slice(perpetualPositions, func(i, j int) bool { - return perpetualPositions[i].GetId() < perpetualPositions[j].GetId() - }) - - settledUpdates[i].SettledSubaccount.PerpetualPositions = perpetualPositions - } -} - -// For each settledUpdate in settledUpdates, updates its SettledSubaccount.AssetPositions -// to reflect settledUpdate.AssetUpdates. -func UpdateAssetPositions( - settledUpdates []types.SettledUpdate, -) { - // Apply the updates. - for i, u := range settledUpdates { - // Build a map of all the Subaccount's Asset Positions by id. - assetPositionsMap := make(map[uint32]*types.AssetPosition) - for _, ap := range u.SettledSubaccount.AssetPositions { - assetPositionsMap[ap.AssetId] = ap - } - - // Update the asset positions. - for _, au := range u.AssetUpdates { - // Check if the `Subaccount` already has a position with the same id. - // If so - we update the size of the existing position, otherwise - // we create a new position. - if ap, exists := assetPositionsMap[au.AssetId]; exists { - curQuantums := ap.GetBigQuantums() - updateQuantums := au.GetBigQuantums() - newQuantums := curQuantums.Add(curQuantums, updateQuantums) - - ap.Quantums = dtypes.NewIntFromBigInt(newQuantums) - - // Handle the case where the position is now closed. - if ap.Quantums.Sign() == 0 { - delete(assetPositionsMap, au.AssetId) - } - } else { - // This subaccount does not have a matching asset position for this update. - - // Create the new asset position. - assetPosition := &types.AssetPosition{ - AssetId: au.AssetId, - Quantums: dtypes.NewIntFromBigInt(au.GetBigQuantums()), - } - - // Add the new asset position to the map. - assetPositionsMap[au.AssetId] = assetPosition - } - } - - // Convert the new AssetPostiion values back into a slice. - assetPositions := make([]*types.AssetPosition, 0, len(assetPositionsMap)) - for _, value := range assetPositionsMap { - assetPositions = append(assetPositions, value) - } - - // Sort the new AssetPositions in ascending order by AssetId. - sort.Slice(assetPositions, func(i, j int) bool { - return assetPositions[i].GetId() < assetPositions[j].GetId() - }) - - settledUpdates[i].SettledSubaccount.AssetPositions = assetPositions - } -} diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 747ff8cfb9..d126bd422f 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -5826,8 +5826,7 @@ func TestGetNetCollateralAndMarginRequirements(t *testing.T) { PerpetualUpdates: tc.perpetualUpdates, } - netCollateral, initialMargin, maintenanceMargin, err := - keeper.GetNetCollateralAndMarginRequirements(ctx, update) + risk, err := keeper.GetNetCollateralAndMarginRequirements(ctx, update) if tc.expectedErr != nil { require.ErrorIs(t, tc.expectedErr, err) @@ -5836,104 +5835,16 @@ func TestGetNetCollateralAndMarginRequirements(t *testing.T) { // https://github.com/stretchr/testify/issues/1116 // for that reason we convert to strings here to make the output more readable if tc.expectedNetCollateral != nil { - require.Equal(t, tc.expectedNetCollateral.String(), netCollateral.String()) + require.Equal(t, tc.expectedNetCollateral.String(), risk.NC.String()) } if tc.expectedInitialMargin != nil { - require.Equal(t, tc.expectedInitialMargin.String(), initialMargin.String()) + require.Equal(t, tc.expectedInitialMargin.String(), risk.IMR.String()) } if tc.expectedMaintenanceMargin != nil { - require.Equal(t, tc.expectedMaintenanceMargin.String(), maintenanceMargin.String()) + require.Equal(t, tc.expectedMaintenanceMargin.String(), risk.MMR.String()) } require.NoError(t, err) } }) } } - -func TestIsValidStateTransitionForUndercollateralizedSubaccount_ZeroMarginRequirements(t *testing.T) { - tests := map[string]struct { - bigCurNetCollateral *big.Int - bigCurInitialMargin *big.Int - bigCurMaintenanceMargin *big.Int - bigNewNetCollateral *big.Int - bigNewMaintenanceMargin *big.Int - - expectedResult types.UpdateResult - }{ - // Tests when current margin requirement is zero and margin requirement increases. - "fails when MMR increases and TNC decreases - negative TNC": { - bigCurNetCollateral: big.NewInt(-1), - bigCurInitialMargin: big.NewInt(0), - bigCurMaintenanceMargin: big.NewInt(0), - bigNewNetCollateral: big.NewInt(-2), - bigNewMaintenanceMargin: big.NewInt(1), - expectedResult: types.StillUndercollateralized, - }, - "fails when MMR increases and TNC stays the same - negative TNC": { - bigCurNetCollateral: big.NewInt(-1), - bigCurInitialMargin: big.NewInt(0), - bigCurMaintenanceMargin: big.NewInt(0), - bigNewNetCollateral: big.NewInt(-1), - bigNewMaintenanceMargin: big.NewInt(1), - expectedResult: types.StillUndercollateralized, - }, - "fails when MMR increases and TNC increases - negative TNC": { - bigCurNetCollateral: big.NewInt(-1), - bigCurInitialMargin: big.NewInt(0), - bigCurMaintenanceMargin: big.NewInt(0), - bigNewNetCollateral: big.NewInt(100), - bigNewMaintenanceMargin: big.NewInt(1), - expectedResult: types.StillUndercollateralized, - }, - // Tests when both margin requirements are zero. - "fails when both new and old MMR are zero and TNC stays the same": { - bigCurNetCollateral: big.NewInt(-1), - bigCurInitialMargin: big.NewInt(0), - bigCurMaintenanceMargin: big.NewInt(0), - bigNewNetCollateral: big.NewInt(-1), - bigNewMaintenanceMargin: big.NewInt(0), - expectedResult: types.StillUndercollateralized, - }, - "fails when both new and old MMR are zero and TNC decrease from negative to negative": { - bigCurNetCollateral: big.NewInt(-1), - bigCurInitialMargin: big.NewInt(0), - bigCurMaintenanceMargin: big.NewInt(0), - bigNewNetCollateral: big.NewInt(-2), - bigNewMaintenanceMargin: big.NewInt(0), - expectedResult: types.StillUndercollateralized, - }, - "succeeds when both new and old MMR are zero and TNC increases": { - bigCurNetCollateral: big.NewInt(-2), - bigCurInitialMargin: big.NewInt(0), - bigCurMaintenanceMargin: big.NewInt(0), - bigNewNetCollateral: big.NewInt(-1), - bigNewMaintenanceMargin: big.NewInt(0), - expectedResult: types.Success, - }, - // Tests when new margin requirement is zero. - "fails when MMR decreased to zero, and TNC increases but is still negative": { - bigCurNetCollateral: big.NewInt(-2), - bigCurInitialMargin: big.NewInt(1), - bigCurMaintenanceMargin: big.NewInt(1), - bigNewNetCollateral: big.NewInt(-1), - bigNewMaintenanceMargin: big.NewInt(0), - expectedResult: types.StillUndercollateralized, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - require.Equal( - t, - tc.expectedResult, - keeper.IsValidStateTransitionForUndercollateralizedSubaccount( - tc.bigCurNetCollateral, - tc.bigCurInitialMargin, - tc.bigCurMaintenanceMargin, - tc.bigNewNetCollateral, - tc.bigNewMaintenanceMargin, - ), - ) - }) - } -} diff --git a/protocol/x/subaccounts/keeper/oimf.go b/protocol/x/subaccounts/lib/oimf.go similarity index 99% rename from protocol/x/subaccounts/keeper/oimf.go rename to protocol/x/subaccounts/lib/oimf.go index f8e87e784a..80068c9b5f 100644 --- a/protocol/x/subaccounts/keeper/oimf.go +++ b/protocol/x/subaccounts/lib/oimf.go @@ -1,4 +1,4 @@ -package keeper +package lib import ( "fmt" diff --git a/protocol/x/subaccounts/keeper/oimf_test.go b/protocol/x/subaccounts/lib/oimf_test.go similarity index 97% rename from protocol/x/subaccounts/keeper/oimf_test.go rename to protocol/x/subaccounts/lib/oimf_test.go index f908720e82..13d9c7acc7 100644 --- a/protocol/x/subaccounts/keeper/oimf_test.go +++ b/protocol/x/subaccounts/lib/oimf_test.go @@ -1,4 +1,4 @@ -package keeper_test +package lib_test import ( "fmt" @@ -7,7 +7,7 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/dtypes" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" - keeper "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" + salib "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/lib" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" "github.com/stretchr/testify/require" ) @@ -364,7 +364,7 @@ func TestGetDeltaOpenInterestFromUpdates(t *testing.T) { tc.panicErr, tc.settledUpdates, ), func() { - keeper.GetDeltaOpenInterestFromUpdates( + salib.GetDeltaOpenInterestFromUpdates( tc.settledUpdates, tc.updateType, ) @@ -373,7 +373,7 @@ func TestGetDeltaOpenInterestFromUpdates(t *testing.T) { return } - perpOpenInterestDelta := keeper.GetDeltaOpenInterestFromUpdates( + perpOpenInterestDelta := salib.GetDeltaOpenInterestFromUpdates( tc.settledUpdates, tc.updateType, ) diff --git a/protocol/x/subaccounts/lib/updates.go b/protocol/x/subaccounts/lib/updates.go new file mode 100644 index 0000000000..0adc890426 --- /dev/null +++ b/protocol/x/subaccounts/lib/updates.go @@ -0,0 +1,478 @@ +package lib + +import ( + "fmt" + "math/big" + "sort" + + errorsmod "cosmossdk.io/errors" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" + assetslib "github.com/dydxprotocol/v4-chain/protocol/x/assets/lib" + perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" +) + +// GetSettledSubaccountWithPerpetuals returns 1. a new settled subaccount given an unsettled subaccount, +// updating the USDC AssetPosition, FundingIndex, and LastFundingPayment fields accordingly +// (does not persist any changes) and 2. a map with perpetual ID as key and last funding +// payment as value (for emitting funding payments to indexer). +func GetSettledSubaccountWithPerpetuals( + subaccount types.Subaccount, + perpInfos perptypes.PerpInfos, +) ( + settledSubaccount types.Subaccount, + fundingPayments map[uint32]dtypes.SerializableInt, +) { + totalNetSettlementPpm := big.NewInt(0) + + newPerpetualPositions := []*types.PerpetualPosition{} + fundingPayments = make(map[uint32]dtypes.SerializableInt) + + // Iterate through and settle all perpetual positions. + for _, p := range subaccount.PerpetualPositions { + perpInfo := perpInfos.MustGet(p.PerpetualId) + + // Call the stateless utility function to get the net settlement and new funding index. + bigNetSettlementPpm, newFundingIndex := perplib.GetSettlementPpmWithPerpetual( + perpInfo.Perpetual, + p.GetBigQuantums(), + p.FundingIndex.BigInt(), + ) + // Record non-zero funding payment (to be later emitted in SubaccountUpdateEvent to indexer). + // Note: Funding payment is the negative of settlement, i.e. positive settlement is equivalent + // to a negative funding payment (position received funding payment) and vice versa. + if bigNetSettlementPpm.BitLen() != 0 { + fundingPayments[p.PerpetualId] = dtypes.NewIntFromBigInt( + new(big.Int).Neg( + new(big.Int).Div(bigNetSettlementPpm, lib.BigIntOneMillion()), + ), + ) + } + + // Aggregate all net settlements. + totalNetSettlementPpm.Add(totalNetSettlementPpm, bigNetSettlementPpm) + + // Update cached funding index of the perpetual position. + newPerpetualPositions = append( + newPerpetualPositions, &types.PerpetualPosition{ + PerpetualId: p.PerpetualId, + Quantums: p.Quantums, + FundingIndex: dtypes.NewIntFromBigInt(newFundingIndex), + }, + ) + } + + newSubaccount := types.Subaccount{ + Id: subaccount.Id, + AssetPositions: subaccount.AssetPositions, + PerpetualPositions: newPerpetualPositions, + MarginEnabled: subaccount.MarginEnabled, + } + newUsdcPosition := new(big.Int).Add( + subaccount.GetUsdcPosition(), + // `Div` implements Euclidean division (unlike Go). When the diviser is positive, + // division result always rounds towards negative infinity. + totalNetSettlementPpm.Div(totalNetSettlementPpm, lib.BigIntOneMillion()), + ) + // TODO(CLOB-993): Remove this function and use `UpdateAssetPositions` instead. + newSubaccount.SetUsdcAssetPosition(newUsdcPosition) + return newSubaccount, fundingPayments +} + +// IsValidStateTransitionForUndercollateralizedSubaccount returns an `UpdateResult` +// denoting whether this state transition is valid. This function accepts the collateral and +// margin requirements of a subaccount before and after an update ("cur" and +// "new", respectively). +// +// This function should only be called if the account is undercollateralized after the update. +// +// A state transition is valid if the subaccount enters a +// "less-or-equally-risky" state after an update. +// i.e.`newNetCollateral / newMaintenanceMargin >= curNetCollateral / curMaintenanceMargin`. +// +// Otherwise, the state transition is invalid. If the account was previously undercollateralized, +// `types.StillUndercollateralized` is returned. If the account was previously +// collateralized and is now undercollateralized, `types.NewlyUndercollateralized` is +// returned. +// +// Note that the inequality `newNetCollateral / newMaintenanceMargin >= curNetCollateral / curMaintenanceMargin` +// has divide-by-zero issue when margin requirements are zero. To make sure the state +// transition is valid, we special case this scenario and only allow state transition that improves net collateral. +func IsValidStateTransitionForUndercollateralizedSubaccount( + riskCur margin.Risk, + riskNew margin.Risk, +) types.UpdateResult { + // Determine whether the subaccount was previously undercollateralized before the update. + var underCollateralizationResult = types.StillUndercollateralized + if riskCur.IMR.Cmp(riskCur.NC) <= 0 { + underCollateralizationResult = types.NewlyUndercollateralized + } + + // If the maintenance margin is increasing, then the subaccount is undercollateralized. + if riskNew.MMR.Cmp(riskCur.MMR) > 0 { + return underCollateralizationResult + } + + // If the maintenance margin is zero, it means the subaccount must have no open positions, and negative net + // collateral. If the net collateral is not improving then this transition is not valid. + if riskNew.MMR.BitLen() == 0 || riskCur.MMR.BitLen() == 0 { + if riskNew.MMR.BitLen() == 0 && + riskCur.MMR.BitLen() == 0 && + riskNew.NC.Cmp(riskCur.NC) > 0 { + return types.Success + } + + return underCollateralizationResult + } + + // Note that here we are effectively checking that + // `newNetCollateral / newMaintenanceMargin >= curNetCollateral / curMaintenanceMargin`. + // However, to avoid rounding errors, we factor this as + // `newNetCollateral * curMaintenanceMargin >= curNetCollateral * newMaintenanceMargin`. + newNcOldMmr := new(big.Int).Mul(riskNew.NC, riskCur.MMR) + oldNcNewMmr := new(big.Int).Mul(riskCur.NC, riskNew.MMR) + + // The subaccount is not well-collateralized, and the state transition leaves the subaccount in a + // "more-risky" state (collateral relative to margin requirements is decreasing). + if oldNcNewMmr.Cmp(newNcOldMmr) > 0 { + return underCollateralizationResult + } + + // The subaccount is in a "less-or-equally-risky" state (margin requirements are decreasing or unchanged, + // collateral relative to margin requirements is decreasing or unchanged). + // This subaccount is undercollateralized in this state, but we still consider this state transition valid. + return types.Success +} + +// ApplyUpdatesToPositions merges a slice of `types.UpdatablePositions` and `types.PositionSize` +// (i.e. concrete types *types.AssetPosition` and `types.AssetUpdate`) into a slice of `types.PositionSize`. +// If a given `PositionSize` shares an ID with an `UpdatablePositionSize`, the update and position are merged +// into a single `PositionSize`. +// +// An error is returned if two updates share the same position id. +// +// Note: There are probably performance implications here for allocating a new slice of PositionSize, +// and for allocating new slices when converting the concrete types to interfaces. However, without doing +// this there would be a lot of duplicate code for calculating changes for both `Assets` and `Perpetuals`. +func ApplyUpdatesToPositions[ + P types.PositionSize, + U types.PositionSize, +](positions []P, updates []U) ([]types.PositionSize, error) { + var result []types.PositionSize = make([]types.PositionSize, 0, len(positions)+len(updates)) + + updateMap := make(map[uint32]types.PositionSize, len(updates)) + updateIndexMap := make(map[uint32]int, len(updates)) + for i, update := range updates { + // Check for non-unique updates (two updates to the same position). + id := update.GetId() + _, exists := updateMap[id] + if exists { + errMsg := fmt.Sprintf("Multiple updates exist for position %v", update.GetId()) + return nil, errorsmod.Wrap(types.ErrNonUniqueUpdatesPosition, errMsg) + } + + updateMap[id] = update + updateIndexMap[id] = i + result = append(result, update) + } + + // Iterate over each position, if the position shares an ID with + // an update, then we "merge" the update and the position into a new `PositionUpdate`. + for _, pos := range positions { + id := pos.GetId() + update, exists := updateMap[id] + if !exists { + result = append(result, pos) + } else { + var newPos = types.NewPositionUpdate(id) + + // Add the position size and update together to get the new size. + var bigNewPositionSize = new(big.Int).Add( + pos.GetBigQuantums(), + update.GetBigQuantums(), + ) + + newPos.SetBigQuantums(bigNewPositionSize) + + // Replace update with `PositionUpdate` + index := updateIndexMap[id] + result[index] = newPos + } + } + + return result, nil +} + +// GetUpdatedAssetPositions filters out all the asset positions on a subaccount that have +// been updated. This will include any asset postions that were closed due to an update. +// TODO(DEC-1295): look into reducing code duplication here using Generics+Reflect. +func GetUpdatedAssetPositions( + update types.SettledUpdate, +) []*types.AssetPosition { + assetIdToPositionMap := make(map[uint32]*types.AssetPosition) + for _, assetPosition := range update.SettledSubaccount.AssetPositions { + assetIdToPositionMap[assetPosition.AssetId] = assetPosition + } + + updatedAssetIds := make(map[uint32]struct{}) + for _, assetUpdate := range update.AssetUpdates { + updatedAssetIds[assetUpdate.AssetId] = struct{}{} + } + + updatedAssetPositions := make([]*types.AssetPosition, 0, len(updatedAssetIds)) + for updatedId := range updatedAssetIds { + assetPosition, exists := assetIdToPositionMap[updatedId] + // If a position does not exist on the subaccount with the asset id of an update, it must + // have been deleted due to quantums becoming 0. This needs to be included in the event, so we + // construct a position with the AssetId of the update and a Quantums value of 0. The other + // properties are left as the default values as a 0-sized position indicates the position is + // closed. + if !exists { + assetPosition = &types.AssetPosition{ + AssetId: updatedId, + Quantums: dtypes.ZeroInt(), + } + } + updatedAssetPositions = append(updatedAssetPositions, assetPosition) + } + + // Sort the asset positions in ascending order by asset id. + sort.Slice(updatedAssetPositions, func(i, j int) bool { + return updatedAssetPositions[i].GetId() < updatedAssetPositions[j].GetId() + }) + + return updatedAssetPositions +} + +// GetUpdatedPerpetualPositions filters out all the perpetual positions on a subaccount that have +// been updated. This will include any perpetual postions that were closed due to an update or that +// received / paid out funding payments.. +func GetUpdatedPerpetualPositions( + update types.SettledUpdate, + fundingPayments map[uint32]dtypes.SerializableInt, +) []*types.PerpetualPosition { + perpetualIdToPositionMap := make(map[uint32]*types.PerpetualPosition) + for _, perpetualPosition := range update.SettledSubaccount.PerpetualPositions { + perpetualIdToPositionMap[perpetualPosition.PerpetualId] = perpetualPosition + } + + // `updatedPerpetualIds` indicates which perpetuals were either explicitly updated + // (through update.PerpetualUpdates) or implicitly updated (had non-zero last funding + // payment). + updatedPerpetualIds := make(map[uint32]struct{}) + for _, perpetualUpdate := range update.PerpetualUpdates { + updatedPerpetualIds[perpetualUpdate.PerpetualId] = struct{}{} + } + // Mark perpetuals with non-zero funding payment also as updated. + for perpetualIdWithNonZeroLastFunding := range fundingPayments { + updatedPerpetualIds[perpetualIdWithNonZeroLastFunding] = struct{}{} + } + + updatedPerpetualPositions := make([]*types.PerpetualPosition, 0, len(updatedPerpetualIds)) + for updatedId := range updatedPerpetualIds { + perpetualPosition, exists := perpetualIdToPositionMap[updatedId] + // If a position does not exist on the subaccount with the perpetual id of an update, it must + // have been deleted due to quantums becoming 0. This needs to be included in the event, so we + // construct a position with the PerpetualId of the update and a Quantums value of 0. The other + // properties are left as the default values as a 0-sized position indicates the position is + // closed and thus the funding index and the side of the position does not matter. + if !exists { + perpetualPosition = &types.PerpetualPosition{ + PerpetualId: updatedId, + Quantums: dtypes.ZeroInt(), + } + } + updatedPerpetualPositions = append(updatedPerpetualPositions, perpetualPosition) + } + + // Sort the perpetual positions in ascending order by perpetual id. + sort.Slice(updatedPerpetualPositions, func(i, j int) bool { + return updatedPerpetualPositions[i].GetId() < updatedPerpetualPositions[j].GetId() + }) + + return updatedPerpetualPositions +} + +// For each settledUpdate in settledUpdates, updates its SettledSubaccount.PerpetualPositions +// to reflect settledUpdate.PerpetualUpdates. +// For newly created positions, use `perpIdToFundingIndex` map to populate the `FundingIndex` field. +func UpdatePerpetualPositions( + settledUpdates []types.SettledUpdate, + perpInfos perptypes.PerpInfos, +) { + // Apply the updates. + for i, u := range settledUpdates { + // Build a map of all the Subaccount's Perpetual Positions by id. + perpetualPositionsMap := make(map[uint32]*types.PerpetualPosition) + for _, pp := range u.SettledSubaccount.PerpetualPositions { + perpetualPositionsMap[pp.PerpetualId] = pp + } + + // Update the perpetual positions. + for _, pu := range u.PerpetualUpdates { + // Check if the `Subaccount` already has a position with the same id. + // If so – we update the size of the existing position, otherwise + // we create a new position. + if pp, exists := perpetualPositionsMap[pu.PerpetualId]; exists { + curQuantums := pp.GetBigQuantums() + updateQuantums := pu.GetBigQuantums() + newQuantums := curQuantums.Add(curQuantums, updateQuantums) + + // Handle the case where the position is now closed. + if newQuantums.Sign() == 0 { + delete(perpetualPositionsMap, pu.PerpetualId) + } + pp.Quantums = dtypes.NewIntFromBigInt(newQuantums) + } else { + // This subaccount does not have a matching position for this update. + // Create the new position. + perpInfo := perpInfos.MustGet(pu.PerpetualId) + perpetualPosition := &types.PerpetualPosition{ + PerpetualId: pu.PerpetualId, + Quantums: dtypes.NewIntFromBigInt(pu.GetBigQuantums()), + FundingIndex: perpInfo.Perpetual.FundingIndex, + } + + // Add the new position to the map. + perpetualPositionsMap[pu.PerpetualId] = perpetualPosition + } + } + + // Convert the new PerpetualPostiion values back into a slice. + perpetualPositions := make([]*types.PerpetualPosition, 0, len(perpetualPositionsMap)) + for _, value := range perpetualPositionsMap { + perpetualPositions = append(perpetualPositions, value) + } + + // Sort the new PerpetualPositions in ascending order by Id. + sort.Slice(perpetualPositions, func(i, j int) bool { + return perpetualPositions[i].GetId() < perpetualPositions[j].GetId() + }) + + settledUpdates[i].SettledSubaccount.PerpetualPositions = perpetualPositions + } +} + +// For each settledUpdate in settledUpdates, updates its SettledSubaccount.AssetPositions +// to reflect settledUpdate.AssetUpdates. +func UpdateAssetPositions( + settledUpdates []types.SettledUpdate, +) { + // Apply the updates. + for i, u := range settledUpdates { + // Build a map of all the Subaccount's Asset Positions by id. + assetPositionsMap := make(map[uint32]*types.AssetPosition) + for _, ap := range u.SettledSubaccount.AssetPositions { + assetPositionsMap[ap.AssetId] = ap + } + + // Update the asset positions. + for _, au := range u.AssetUpdates { + // Check if the `Subaccount` already has a position with the same id. + // If so - we update the size of the existing position, otherwise + // we create a new position. + if ap, exists := assetPositionsMap[au.AssetId]; exists { + curQuantums := ap.GetBigQuantums() + updateQuantums := au.GetBigQuantums() + newQuantums := curQuantums.Add(curQuantums, updateQuantums) + + ap.Quantums = dtypes.NewIntFromBigInt(newQuantums) + + // Handle the case where the position is now closed. + if ap.Quantums.Sign() == 0 { + delete(assetPositionsMap, au.AssetId) + } + } else { + // This subaccount does not have a matching asset position for this update. + + // Create the new asset position. + assetPosition := &types.AssetPosition{ + AssetId: au.AssetId, + Quantums: dtypes.NewIntFromBigInt(au.GetBigQuantums()), + } + + // Add the new asset position to the map. + assetPositionsMap[au.AssetId] = assetPosition + } + } + + // Convert the new AssetPostiion values back into a slice. + assetPositions := make([]*types.AssetPosition, 0, len(assetPositionsMap)) + for _, value := range assetPositionsMap { + assetPositions = append(assetPositions, value) + } + + // Sort the new AssetPositions in ascending order by AssetId. + sort.Slice(assetPositions, func(i, j int) bool { + return assetPositions[i].GetId() < assetPositions[j].GetId() + }) + + settledUpdates[i].SettledSubaccount.AssetPositions = assetPositions + } +} + +// GetRiskForSubaccount returns the risk value of the `Subaccount` after updates are applied. +// It is used to get information about speculative changes to the `Subaccount`. +// The input subaccount must be settled. +// +// The provided update can also be "zeroed" in order to get information about +// the current state of the subaccount (i.e. with no changes). +// +// If two position updates reference the same position, an error is returned. +func GetRiskForSubaccount( + settledUpdate types.SettledUpdate, + perpInfos perptypes.PerpInfos, +) ( + risk margin.Risk, + err error, +) { + // Initialize return values. + risk = margin.ZeroRisk() + + // Merge updates and assets. + assetSizes, err := ApplyUpdatesToPositions( + settledUpdate.SettledSubaccount.AssetPositions, + settledUpdate.AssetUpdates, + ) + if err != nil { + return risk, err + } + + // Merge updates and perpetuals. + perpetualSizes, err := ApplyUpdatesToPositions( + settledUpdate.SettledSubaccount.PerpetualPositions, + settledUpdate.PerpetualUpdates, + ) + if err != nil { + return risk, err + } + + // Iterate over all assets and updates and calculate change to net collateral and margin requirements. + for _, size := range assetSizes { + r, err := assetslib.GetNetCollateralAndMarginRequirements( + size.GetId(), + size.GetBigQuantums(), + ) + if err != nil { + return risk, err + } + risk.AddInPlace(r) + } + + // Iterate over all perpetuals and updates and calculate change to net collateral and margin requirements. + for _, size := range perpetualSizes { + perpInfo := perpInfos.MustGet(size.GetId()) + r := perplib.GetNetCollateralAndMarginRequirements( + perpInfo.Perpetual, + perpInfo.Price, + perpInfo.LiquidityTier, + size.GetBigQuantums(), + ) + risk.AddInPlace(r) + } + + return risk, nil +} diff --git a/protocol/x/subaccounts/lib/updates_test.go b/protocol/x/subaccounts/lib/updates_test.go new file mode 100644 index 0000000000..7760d6844d --- /dev/null +++ b/protocol/x/subaccounts/lib/updates_test.go @@ -0,0 +1,264 @@ +package lib_test + +import ( + "math/big" + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + pricetypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/stretchr/testify/require" +) + +func TestIsValidStateTransitionForUndercollateralizedSubaccount_ZeroMarginRequirements(t *testing.T) { + tests := map[string]struct { + oldNC *big.Int + oldIMR *big.Int + oldMMR *big.Int + newNC *big.Int + newMMR *big.Int + + expectedResult types.UpdateResult + }{ + // Tests when current margin requirement is zero and margin requirement increases. + "fails when MMR increases and TNC decreases - negative TNC": { + oldNC: big.NewInt(-1), + oldIMR: big.NewInt(0), + oldMMR: big.NewInt(0), + newNC: big.NewInt(-2), + newMMR: big.NewInt(1), + expectedResult: types.StillUndercollateralized, + }, + "fails when MMR increases and TNC stays the same - negative TNC": { + oldNC: big.NewInt(-1), + oldIMR: big.NewInt(0), + oldMMR: big.NewInt(0), + newNC: big.NewInt(-1), + newMMR: big.NewInt(1), + expectedResult: types.StillUndercollateralized, + }, + "fails when MMR increases and TNC increases - negative TNC": { + oldNC: big.NewInt(-1), + oldIMR: big.NewInt(0), + oldMMR: big.NewInt(0), + newNC: big.NewInt(100), + newMMR: big.NewInt(1), + expectedResult: types.StillUndercollateralized, + }, + // Tests when both margin requirements are zero. + "fails when both new and old MMR are zero and TNC stays the same": { + oldNC: big.NewInt(-1), + oldIMR: big.NewInt(0), + oldMMR: big.NewInt(0), + newNC: big.NewInt(-1), + newMMR: big.NewInt(0), + expectedResult: types.StillUndercollateralized, + }, + "fails when both new and old MMR are zero and TNC decrease from negative to negative": { + oldNC: big.NewInt(-1), + oldIMR: big.NewInt(0), + oldMMR: big.NewInt(0), + newNC: big.NewInt(-2), + newMMR: big.NewInt(0), + expectedResult: types.StillUndercollateralized, + }, + "succeeds when both new and old MMR are zero and TNC increases": { + oldNC: big.NewInt(-2), + oldIMR: big.NewInt(0), + oldMMR: big.NewInt(0), + newNC: big.NewInt(-1), + newMMR: big.NewInt(0), + expectedResult: types.Success, + }, + // Tests when new margin requirement is zero. + "fails when MMR decreased to zero, and TNC increases but is still negative": { + oldNC: big.NewInt(-2), + oldIMR: big.NewInt(1), + oldMMR: big.NewInt(1), + newNC: big.NewInt(-1), + newMMR: big.NewInt(0), + expectedResult: types.StillUndercollateralized, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal( + t, + tc.expectedResult, + lib.IsValidStateTransitionForUndercollateralizedSubaccount( + margin.Risk{ + NC: tc.oldNC, + IMR: tc.oldIMR, + MMR: tc.oldMMR, + }, + margin.Risk{ + NC: tc.newNC, + MMR: tc.newMMR, + }, + ), + ) + }) + } +} + +func TestGetRiskForSubaccount(t *testing.T) { + subaccountId := types.SubaccountId{Owner: "test", Number: 1} + tests := map[string]struct { + settledUpdate types.SettledUpdate + perpInfos perptypes.PerpInfos + expectedRisk margin.Risk + expectedErr error + }{ + "no account": { + settledUpdate: types.SettledUpdate{}, + perpInfos: perptypes.PerpInfos{}, + expectedRisk: margin.ZeroRisk(), + expectedErr: nil, + }, + "no updates": { + settledUpdate: types.SettledUpdate{ + SettledSubaccount: types.Subaccount{ + Id: &subaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + createPerpPosition(1, big.NewInt(100), big.NewInt(0)), + }, + AssetPositions: createUsdcAmount(big.NewInt(100)), + }, + PerpetualUpdates: []types.PerpetualUpdate{}, + AssetUpdates: []types.AssetUpdate{}, + }, + perpInfos: perptypes.PerpInfos{ + 1: createPerpInfo(1, -6, 100, 0), + }, + expectedRisk: margin.Risk{ + NC: big.NewInt(100*100 + 100), + IMR: big.NewInt(100 * 100 * 0.1), + MMR: big.NewInt(100 * 100 * 0.1 * 0.5), + }, + expectedErr: nil, + }, + "one update": { + settledUpdate: types.SettledUpdate{ + SettledSubaccount: types.Subaccount{ + Id: &subaccountId, + PerpetualPositions: []*types.PerpetualPosition{ + createPerpPosition(1, big.NewInt(100), big.NewInt(0)), + }, + AssetPositions: createUsdcAmount(big.NewInt(100)), + }, + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: 2, + BigQuantumsDelta: big.NewInt(-25), + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: assettypes.AssetUsdc.Id, + BigQuantumsDelta: big.NewInt(10), + }, + }, + }, + perpInfos: perptypes.PerpInfos{ + 1: createPerpInfo(1, -6, 100, 0), + 2: createPerpInfo(2, -6, 200, 0), + }, + expectedRisk: margin.Risk{ + NC: big.NewInt((100*100 + 100) + (-25*200 + 10)), + IMR: big.NewInt((100 * 100 * 0.1) + (25 * 200 * 0.1)), + MMR: big.NewInt((100 * 100 * 0.1 * 0.5) + (25 * 200 * 0.1 * 0.5)), + }, + expectedErr: nil, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + risk, err := lib.GetRiskForSubaccount(tc.settledUpdate, tc.perpInfos) + require.Equal(t, tc.expectedRisk, risk) + if tc.expectedErr != nil { + require.Equal(t, tc.expectedErr, err) + } else { + require.Nil(t, err) + } + }) + } +} + +func TestGetRiskForSubaccount_Panic(t *testing.T) { + sa := types.SettledUpdate{ + SettledSubaccount: types.Subaccount{ + Id: &types.SubaccountId{Owner: "test", Number: 1}, + PerpetualPositions: []*types.PerpetualPosition{ + createPerpPosition(1, big.NewInt(100), big.NewInt(0)), + }, + AssetPositions: createUsdcAmount(big.NewInt(100)), + }, + PerpetualUpdates: []types.PerpetualUpdate{}, + AssetUpdates: []types.AssetUpdate{}, + } + emptyPerpInfos := perptypes.PerpInfos{} + + // Panics since relevant perpetual information cannot be found. + require.Panics(t, func() { + _, _ = lib.GetRiskForSubaccount(sa, emptyPerpInfos) + }) +} + +func createPerpPosition( + id uint32, + quantums *big.Int, + fundingIndex *big.Int, +) *types.PerpetualPosition { + return &types.PerpetualPosition{ + PerpetualId: id, + Quantums: dtypes.NewIntFromBigInt(quantums), + FundingIndex: dtypes.NewIntFromBigInt(fundingIndex), + } +} + +func createUsdcAmount(amount *big.Int) []*types.AssetPosition { + return []*types.AssetPosition{ + { + AssetId: assettypes.AssetUsdc.Id, + Quantums: dtypes.NewIntFromBigInt(amount), + }, + } +} + +func createPerpInfo( + id uint32, + atomicResolution int32, + price uint64, + priceExponent int32, +) perptypes.PerpInfo { + return perptypes.PerpInfo{ + Perpetual: perptypes.Perpetual{ + Params: perptypes.PerpetualParams{ + Id: id, + Ticker: "test ticker", + MarketId: id, + AtomicResolution: atomicResolution, + LiquidityTier: id, + }, + FundingIndex: dtypes.NewInt(0), + OpenInterest: dtypes.NewInt(0), + }, + Price: pricetypes.MarketPrice{ + Id: id, + Exponent: priceExponent, + Price: price, + }, + LiquidityTier: perptypes.LiquidityTier{ + Id: id, + InitialMarginPpm: 100_000, + MaintenanceFractionPpm: 500_000, + OpenInterestLowerCap: 0, + OpenInterestUpperCap: 0, + }, + } +} diff --git a/protocol/x/subaccounts/types/errors.go b/protocol/x/subaccounts/types/errors.go index e36de86d00..e976d47143 100644 --- a/protocol/x/subaccounts/types/errors.go +++ b/protocol/x/subaccounts/types/errors.go @@ -65,15 +65,19 @@ var ( 403, "cannot revert perpetual open interest for OIMF calculation", ) - ErrPerpetualInfoDoesNotExist = errorsmod.Register( - ModuleName, - 404, - "PerpetualInfo does not exist in map", - ) // 500 - 599: transfer related. ErrAssetTransferQuantumsNotPositive = errorsmod.Register( ModuleName, 500, "asset transfer quantums is not positive") ErrAssetTransferThroughBankNotImplemented = errorsmod.Register( ModuleName, 501, "asset transfer (other than USDC) through the bank module is not implemented") + + // 600 - 699: safety heap related. + ErrSafetyHeapEmpty = errorsmod.Register(ModuleName, 600, "safety heap is empty") + ErrSafetyHeapSubaccountNotFoundAtIndex = errorsmod.Register( + ModuleName, + 601, + "subaccount not found at index in safety heap", + ) + ErrSafetyHeapSubaccountIndexNotFound = errorsmod.Register(ModuleName, 602, "subaccount index not found") ) diff --git a/protocol/x/subaccounts/types/expected_keepers.go b/protocol/x/subaccounts/types/expected_keepers.go index 2e3372c973..dd8c9f0e62 100644 --- a/protocol/x/subaccounts/types/expected_keepers.go +++ b/protocol/x/subaccounts/types/expected_keepers.go @@ -25,23 +25,6 @@ type ProductKeeper interface { type AssetsKeeper interface { ProductKeeper - GetNetCollateral( - ctx sdk.Context, - id uint32, - bigQuantums *big.Int, - ) ( - bigNetCollateralQuoteQuantums *big.Int, - err error, - ) - GetMarginRequirements( - ctx sdk.Context, - id uint32, - bigQuantums *big.Int, - ) ( - bigInitialMarginQuoteQuantums *big.Int, - bigMaintenanceMarginQuoteQuantums *big.Int, - err error, - ) ConvertAssetToCoin( ctx sdk.Context, assetId uint32, @@ -55,22 +38,20 @@ type AssetsKeeper interface { type PerpetualsKeeper interface { ProductKeeper - GetSettlementPpm( + GetPerpetual( ctx sdk.Context, perpetualId uint32, - quantums *big.Int, - index *big.Int, ) ( - bigNetSettlement *big.Int, - newFundingIndex *big.Int, + perpetual perptypes.Perpetual, err error, ) - GetPerpetual( + GetPerpetualAndMarketPrice( ctx sdk.Context, perpetualId uint32, ) ( - perpetual perptypes.Perpetual, - err error, + perptypes.Perpetual, + pricestypes.MarketPrice, + error, ) GetPerpetualAndMarketPriceAndLiquidityTier( ctx sdk.Context, @@ -81,6 +62,13 @@ type PerpetualsKeeper interface { perptypes.LiquidityTier, error, ) + GetLiquidityTier( + ctx sdk.Context, + id uint32, + ) ( + perptypes.LiquidityTier, + error, + ) GetAllPerpetuals(ctx sdk.Context) []perptypes.Perpetual GetInsuranceFundName(ctx sdk.Context, perpetualId uint32) (string, error) GetInsuranceFundModuleAddress(ctx sdk.Context, perpetualId uint32) (sdk.AccAddress, error) diff --git a/protocol/x/subaccounts/types/keys.go b/protocol/x/subaccounts/types/keys.go index 0d6089d20a..f67d476082 100644 --- a/protocol/x/subaccounts/types/keys.go +++ b/protocol/x/subaccounts/types/keys.go @@ -19,4 +19,10 @@ const ( // Suffix for the store key to the last block a negative TNC subaccount was seen in state for the // cross collateral pool. CrossCollateralSuffix = "cross" + + // Safety Heap + SafetyHeapStorePrefix = "SH" + SafetyHeapSubaccountIdsPrefix = "Heap/" + SafetyHeapSubaccountToIndexPrefix = "Idx/" + SafetyHeapLengthPrefix = "Len/" ) diff --git a/protocol/x/subaccounts/types/subaccount.go b/protocol/x/subaccounts/types/subaccount.go index 58ba138bd0..0b675d650f 100644 --- a/protocol/x/subaccounts/types/subaccount.go +++ b/protocol/x/subaccounts/types/subaccount.go @@ -17,6 +17,13 @@ const ( // BaseQuantums is used to represent an amount in base quantums. type BaseQuantums uint64 +type SafetyHeapPositionSide uint + +const ( + Long SafetyHeapPositionSide = iota + Short +) + // Get the BaseQuantum value in *big.Int. func (bq BaseQuantums) ToBigInt() *big.Int { return new(big.Int).SetUint64(bq.ToUint64()) diff --git a/protocol/x/subaccounts/types/types.go b/protocol/x/subaccounts/types/types.go index cf4ac34475..0212e23791 100644 --- a/protocol/x/subaccounts/types/types.go +++ b/protocol/x/subaccounts/types/types.go @@ -5,6 +5,7 @@ import ( "math/rand" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" ) type SubaccountsKeeper interface { @@ -14,9 +15,7 @@ type SubaccountsKeeper interface { ctx sdk.Context, update Update, ) ( - bigNetCollateral *big.Int, - bigInitialMargin *big.Int, - bigMaintenanceMargin *big.Int, + risk margin.Risk, err error, ) CanUpdateSubaccounts( diff --git a/protocol/x/vault/keeper/keeper.go b/protocol/x/vault/keeper/keeper.go index 4ff8e98253..deca94acf4 100644 --- a/protocol/x/vault/keeper/keeper.go +++ b/protocol/x/vault/keeper/keeper.go @@ -7,20 +7,22 @@ import ( storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - clobKeeper types.ClobKeeper - perpetualsKeeper types.PerpetualsKeeper - pricesKeeper types.PricesKeeper - sendingKeeper types.SendingKeeper - subaccountsKeeper types.SubaccountsKeeper - authorities map[string]struct{} + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + clobKeeper types.ClobKeeper + perpetualsKeeper types.PerpetualsKeeper + pricesKeeper types.PricesKeeper + sendingKeeper types.SendingKeeper + subaccountsKeeper types.SubaccountsKeeper + indexerEventManager indexer_manager.IndexerEventManager + authorities map[string]struct{} } ) @@ -32,20 +34,26 @@ func NewKeeper( pricesKeeper types.PricesKeeper, sendingKeeper types.SendingKeeper, subaccountsKeeper types.SubaccountsKeeper, + indexerEventManager indexer_manager.IndexerEventManager, authorities []string, ) *Keeper { return &Keeper{ - cdc: cdc, - storeKey: storeKey, - clobKeeper: clobKeeper, - perpetualsKeeper: perpetualsKeeper, - pricesKeeper: pricesKeeper, - sendingKeeper: sendingKeeper, - subaccountsKeeper: subaccountsKeeper, - authorities: lib.UniqueSliceToSet(authorities), + cdc: cdc, + storeKey: storeKey, + clobKeeper: clobKeeper, + perpetualsKeeper: perpetualsKeeper, + pricesKeeper: pricesKeeper, + sendingKeeper: sendingKeeper, + subaccountsKeeper: subaccountsKeeper, + indexerEventManager: indexerEventManager, + authorities: lib.UniqueSliceToSet(authorities), } } +func (k Keeper) GetIndexerEventManager() indexer_manager.IndexerEventManager { + return k.indexerEventManager +} + func (k Keeper) HasAuthority(authority string) bool { _, ok := k.authorities[authority] return ok diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index 94e3e838d3..cefeb598f4 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -7,6 +7,8 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" + "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/log" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" @@ -25,6 +27,7 @@ func (k Keeper) RefreshAllVaultOrders(ctx sdk.Context) { defer totalSharesIterator.Close() for ; totalSharesIterator.Valid(); totalSharesIterator.Next() { vaultId, err := types.GetVaultIdFromStateKey(totalSharesIterator.Key()) + if err != nil { log.ErrorLogWithError(ctx, "Failed to get vault ID from state key", err) continue @@ -85,7 +88,7 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) ( err := k.clobKeeper.HandleMsgCancelOrder(ctx, clobtypes.NewMsgCancelOrderStateful( order.OrderId, uint32(ctx.BlockTime().Unix())+orderExpirationSeconds, - )) + ), true) if err != nil { log.ErrorLogWithError(ctx, "Failed to cancel order", err, "order", order, "vaultId", vaultId) } @@ -95,24 +98,52 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) ( ) } } - // Place new CLOB orders. ordersToPlace, err := k.GetVaultClobOrders(ctx, vaultId) if err != nil { log.ErrorLogWithError(ctx, "Failed to get vault clob orders to place", err, "vaultId", vaultId) return err } - for _, order := range ordersToPlace { + + for i, order := range ordersToPlace { err := k.PlaceVaultClobOrder(ctx, order) if err != nil { log.ErrorLogWithError(ctx, "Failed to place order", err, "order", order, "vaultId", vaultId) } + vaultId.IncrCounterWithLabels( metrics.VaultPlaceOrder, metrics.GetLabelForBoolValue(metrics.Success, err == nil), ) - } + // Send indexer messages. We expect ordersToCancel and ordersToPlace to have the same length + // and the order to place at each index to be a replacement of the order to cancel at the same index. + replacedOrder := ordersToCancel[i] + if replacedOrder == nil { + k.GetIndexerEventManager().AddTxnEvent( + ctx, + indexerevents.SubtypeStatefulOrder, + indexerevents.StatefulOrderEventVersion, + indexer_manager.GetBytes( + indexerevents.NewLongTermOrderPlacementEvent( + *order, + ), + ), + ) + } else { + k.GetIndexerEventManager().AddTxnEvent( + ctx, + indexerevents.SubtypeStatefulOrder, + indexerevents.StatefulOrderEventVersion, + indexer_manager.GetBytes( + indexerevents.NewLongTermOrderReplacementEvent( + replacedOrder.OrderId, + *order, + ), + ), + ) + } + } return nil } diff --git a/protocol/x/vault/keeper/orders_test.go b/protocol/x/vault/keeper/orders_test.go index 54715b8164..d8b8f01431 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -7,6 +7,10 @@ import ( "github.com/cometbft/cometbft/types" "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/indexer" + indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" + "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" + "github.com/dydxprotocol/v4-chain/protocol/indexer/msgsender" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" @@ -105,11 +109,16 @@ func TestRefreshAllVaultOrders(t *testing.T) { activationThresholdQuoteQuantums: big.NewInt(123_456_789), }, } - for name, tc := range tests { t.Run(name, func(t *testing.T) { + // Enable testapp's indexer event manager + msgSender := msgsender.NewIndexerMessageSenderInMemoryCollector() + appOpts := map[string]interface{}{ + indexer.MsgSenderInstanceForTest: msgSender, + } + // Initialize tApp and ctx (in deliverTx mode). - tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + tApp := testapp.NewTestAppBuilder(t).WithAppOptions(appOpts).WithGenesisDocFn(func() (genesis types.GenesisDoc) { genesis = testapp.DefaultGenesis() // Initialize each vault with quote quantums to be able to place orders. testapp.UpdateGenesisDocWithAppStateForModule( @@ -160,6 +169,7 @@ func TestRefreshAllVaultOrders(t *testing.T) { // Simulate vault orders placed in last block. numPreviousOrders := 0 + previousOrders := make(map[vaulttypes.VaultId][]*clobtypes.Order) for i, vaultId := range tc.vaultIds { if tc.totalShares[i].Sign() > 0 && tc.assetQuantums[i].Cmp(tc.activationThresholdQuoteQuantums) >= 0 { orders, err := tApp.App.VaultKeeper.GetVaultClobOrders( @@ -171,6 +181,7 @@ func TestRefreshAllVaultOrders(t *testing.T) { err := tApp.App.VaultKeeper.PlaceVaultClobOrder(ctx, order) require.NoError(t, err) } + previousOrders[vaultId] = orders numPreviousOrders += len(orders) } } @@ -183,13 +194,34 @@ func TestRefreshAllVaultOrders(t *testing.T) { // cancelled and orders from this block have been placed. numExpectedOrders := 0 allExpectedOrderIds := make(map[clobtypes.OrderId]bool) - for i, vaultId := range tc.vaultIds { - if tc.totalShares[i].Sign() > 0 && tc.assetQuantums[i].Cmp(tc.activationThresholdQuoteQuantums) >= 0 { + expectedIndexerEvents := make([]indexer_manager.IndexerTendermintEvent, 0) + indexerEventIndex := 0 + for vault_index, vaultId := range tc.vaultIds { + if tc.totalShares[vault_index].Sign() > 0 && + tc.assetQuantums[vault_index].Cmp(tc.activationThresholdQuoteQuantums) >= 0 { expectedOrders, err := tApp.App.VaultKeeper.GetVaultClobOrders(ctx, vaultId) require.NoError(t, err) numExpectedOrders += len(expectedOrders) - for _, order := range expectedOrders { + ordersToCancel := previousOrders[vaultId] + for i, order := range expectedOrders { allExpectedOrderIds[order.OrderId] = true + orderToCancel := ordersToCancel[i] + event := indexer_manager.IndexerTendermintEvent{ + Subtype: indexerevents.SubtypeStatefulOrder, + OrderingWithinBlock: &indexer_manager.IndexerTendermintEvent_TransactionIndex{ + TransactionIndex: 0, + }, + EventIndex: uint32(indexerEventIndex), + Version: indexerevents.StatefulOrderEventVersion, + DataBytes: indexer_manager.GetBytes( + indexerevents.NewLongTermOrderReplacementEvent( + orderToCancel.OrderId, + *order, + ), + ), + } + indexerEventIndex += 1 + expectedIndexerEvents = append(expectedIndexerEvents, event) } } } @@ -198,6 +230,13 @@ func TestRefreshAllVaultOrders(t *testing.T) { for _, order := range allStatefulOrders { require.True(t, allExpectedOrderIds[order.OrderId]) } + + // test that the indexer events emitted are as expected + block := tApp.App.VaultKeeper.GetIndexerEventManager().ProduceBlock(ctx) + require.Len(t, block.Events, numExpectedOrders) + for i, event := range block.Events { + require.Equal(t, expectedIndexerEvents[i], *event) + } }) } } diff --git a/protocol/x/vault/keeper/vault.go b/protocol/x/vault/keeper/vault.go index 1143ca6c1c..1d59e2e1c3 100644 --- a/protocol/x/vault/keeper/vault.go +++ b/protocol/x/vault/keeper/vault.go @@ -16,7 +16,7 @@ func (k Keeper) GetVaultEquity( ctx sdk.Context, vaultId types.VaultId, ) (*big.Int, error) { - netCollateral, _, _, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + risk, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{ SubaccountId: *vaultId.ToSubaccountId(), @@ -25,7 +25,7 @@ func (k Keeper) GetVaultEquity( if err != nil { return nil, err } - return netCollateral, nil + return risk.NC, nil } // GetVaultInventory returns the inventory of a vault in a given perpeutal (in base quantums). diff --git a/protocol/x/vault/types/expected_keepers.go b/protocol/x/vault/types/expected_keepers.go index af355239d9..c55ba732db 100644 --- a/protocol/x/vault/types/expected_keepers.go +++ b/protocol/x/vault/types/expected_keepers.go @@ -1,9 +1,8 @@ package types import ( - "math/big" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib/margin" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" @@ -23,6 +22,7 @@ type ClobKeeper interface { HandleMsgCancelOrder( ctx sdk.Context, msg *clobtypes.MsgCancelOrder, + isInternalOrder bool, ) (err error) HandleMsgPlaceOrder( ctx sdk.Context, @@ -61,9 +61,7 @@ type SubaccountsKeeper interface { ctx sdk.Context, update satypes.Update, ) ( - bigNetCollateral *big.Int, - bigInitialMargin *big.Int, - bigMaintenanceMargin *big.Int, + risk margin.Risk, err error, ) GetSubaccount(