From e880b133711f6c3f55a72c35e94aea4b5b68624d Mon Sep 17 00:00:00 2001 From: ttl33 <19664986+ttl33@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:12:27 -0500 Subject: [PATCH] [CORE-859] `add new market` widget: gov proposal submission and query (#111) * submits gov proposal * fix wrapping for update clob pair + add example commands * define composer + update existing test * symbol -> ticker * bump package version * add comments * fix lint * format * format + comment * revert package bump * format..pls * add methods to submit gov proposal and query all proposals * add wallet + fix client + wait longer * query comment * export title and summary helpers * nit new name --- v4-client-js/examples/gov_add_new_market.ts | 118 ++++-------------- v4-client-js/package-lock.json | 4 +- v4-client-js/package.json | 2 +- v4-client-js/src/clients/composite-client.ts | 99 ++++++++++++++- v4-client-js/src/clients/modules/composer.ts | 3 +- v4-client-js/src/clients/modules/get.ts | 39 +++++- .../src/clients/modules/proto-includes.ts | 8 +- v4-client-js/src/clients/types.ts | 26 ++++ v4-client-js/src/lib/utils.ts | 24 ++++ 9 files changed, 215 insertions(+), 108 deletions(-) diff --git a/v4-client-js/examples/gov_add_new_market.ts b/v4-client-js/examples/gov_add_new_market.ts index 1c401611..fc628b2b 100644 --- a/v4-client-js/examples/gov_add_new_market.ts +++ b/v4-client-js/examples/gov_add_new_market.ts @@ -1,16 +1,13 @@ -import { EncodeObject, Registry } from '@cosmjs/proto-signing'; import Long from 'long'; -import { BECH32_PREFIX } from '../src'; +import { GovAddNewMarketParams, LocalWallet, ProposalStatus } from '../src'; import { CompositeClient } from '../src/clients/composite-client'; -import { Network } from '../src/clients/constants'; -import { generateRegistry } from '../src/clients/lib/registry'; -import { Composer } from '../src/clients/modules/composer'; -import LocalWallet from '../src/clients/modules/local-wallet'; +import { BECH32_PREFIX, Network } from '../src/clients/constants'; +import { getGovAddNewMarketSummary, getGovAddNewMarketTitle, sleep } from '../src/lib/utils'; import { DYDX_LOCAL_MNEMONIC } from './constants'; const INITIAL_DEPOSIT_AMOUNT = 10_000_000_000_000; // 10,000 whole native tokens. -const MOCK_DATA = { +const MOCK_DATA: GovAddNewMarketParams = { // common id: 34, ticker: 'BONK-USD', @@ -32,7 +29,6 @@ const MOCK_DATA = { // x/perpetuals liquidityTier: 2, - defaultFundingPpm: 0, atomicResolution: -1, // x/clob @@ -60,104 +56,36 @@ async function test(): Promise { console.log('**Start**'); const wallet = await LocalWallet.fromMnemonic(DYDX_LOCAL_MNEMONIC, BECH32_PREFIX); + console.log(wallet); + const network = Network.local(); const client = await CompositeClient.connect(network); console.log('**Client**'); console.log(client); - const composer: Composer = client.validatorClient.post.composer; - const registry: Registry = generateRegistry(); - const msgs: EncodeObject[] = []; - - // x/prices.MsgCreateOracleMarket - const createOracleMarket = composer.composeMsgCreateOracleMarket( - MOCK_DATA.id, - MOCK_DATA.ticker, - MOCK_DATA.priceExponent, - MOCK_DATA.minExchanges, - MOCK_DATA.minPriceChange, - MOCK_DATA.exchangeConfigJson, - ); - - // x/perpetuals.MsgCreatePerpetual - const createPerpetual = composer.composeMsgCreatePerpetual( - MOCK_DATA.id, - MOCK_DATA.id, - MOCK_DATA.ticker, - MOCK_DATA.atomicResolution, - MOCK_DATA.defaultFundingPpm, - MOCK_DATA.liquidityTier, - ); - - // x/clob.MsgCreateClobPair - const createClobPair = composer.composeMsgCreateClobPair( - MOCK_DATA.id, - MOCK_DATA.id, - MOCK_DATA.quantumConversionExponent, - MOCK_DATA.stepBaseQuantums, - MOCK_DATA.subticksPerTick, - ); - - // x/clob.MsgUpdateClobPair - const updateClobPair = composer.composeMsgUpdateClobPair( - MOCK_DATA.id, - MOCK_DATA.id, - MOCK_DATA.quantumConversionExponent, - MOCK_DATA.stepBaseQuantums, - MOCK_DATA.subticksPerTick, - ); - - // x/delaymsg.MsgDelayMessage - const delayMessage = composer.composeMsgDelayMessage( - // IMPORTANT: must wrap messages in Any type to fit into delaymsg. - composer.wrapMessageAsAny(registry, updateClobPair), - MOCK_DATA.delayBlocks, - ); - msgs.push(createOracleMarket); - msgs.push(createPerpetual); - msgs.push(createClobPair); - msgs.push(delayMessage); - - // x/gov.v1.MsgSubmitProposal - const submitProposal = composer.composeMsgSubmitProposal( - getTitle(MOCK_DATA.ticker), - INITIAL_DEPOSIT_AMOUNT, - client.validatorClient.config.denoms, - getSummary(MOCK_DATA.ticker, MOCK_DATA.delayBlocks), - // IMPORTANT: must wrap messages in Any type for gov's submit proposal. - composer.wrapMessageArrAsAny(registry, msgs), - wallet.address!, // proposer - ); - - console.log('**Submit Proposal**'); - console.log(submitProposal); - - const tx = await client.send( + const tx = await client.submitGovAddNewMarketProposal( wallet, - () => Promise.resolve([submitProposal]), - false, - client.validatorClient.post.defaultDydxGasPrice, - undefined, - () => client.validatorClient.post.account( - wallet.address!, - undefined, - ), + MOCK_DATA, + getGovAddNewMarketTitle(MOCK_DATA.ticker), + getGovAddNewMarketSummary(MOCK_DATA.ticker, MOCK_DATA.delayBlocks), + INITIAL_DEPOSIT_AMOUNT, ); console.log('**Tx**'); console.log(tx); -} -function getTitle( - ticker: string, -): string { - return `Add ${ticker} perpetual market`; -} + await sleep(5000); -function getSummary( - ticker: string, - delayBlocks: number, -): string { - return `Add the x/prices, x/perpetuals and x/clob parameters needed for a ${ticker} perpetual market. Create the market in INITIALIZING status and transition it to ACTIVE status after ${delayBlocks} blocks.`; + const depositProposals = await client.validatorClient.get.getAllGovProposals( + ProposalStatus.PROPOSAL_STATUS_DEPOSIT_PERIOD, + ); + console.log('**Deposit Proposals**'); + console.log(depositProposals); + + const votingProposals = await client.validatorClient.get.getAllGovProposals( + ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD, + ); + console.log('**Voting Proposals**'); + console.log(votingProposals); } test().catch((error) => { diff --git a/v4-client-js/package-lock.json b/v4-client-js/package-lock.json index 3352e963..4638b548 100644 --- a/v4-client-js/package-lock.json +++ b/v4-client-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dydxprotocol/v4-client-js", - "version": "1.0.15", + "version": "1.0.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@dydxprotocol/v4-client-js", - "version": "1.0.15", + "version": "1.0.16", "license": "AGPL-3.0", "dependencies": { "@cosmjs/amino": "^0.32.1", diff --git a/v4-client-js/package.json b/v4-client-js/package.json index 2678c380..4dc7b3f2 100644 --- a/v4-client-js/package.json +++ b/v4-client-js/package.json @@ -1,6 +1,6 @@ { "name": "@dydxprotocol/v4-client-js", - "version": "1.0.15", + "version": "1.0.16", "description": "General client library for the new dYdX system (v4 decentralized)", "main": "build/src/index.js", "scripts": { diff --git a/v4-client-js/src/clients/composite-client.ts b/v4-client-js/src/clients/composite-client.ts index aa08ecaa..2fcd3048 100644 --- a/v4-client-js/src/clients/composite-client.ts +++ b/v4-client-js/src/clients/composite-client.ts @@ -9,7 +9,7 @@ import Long from 'long'; import protobuf from 'protobufjs'; import { isStatefulOrder, verifyOrderFlags } from '../lib/validation'; -import { OrderFlags } from '../types'; +import { GovAddNewMarketParams, OrderFlags } from '../types'; import { Network, OrderExecution, @@ -31,6 +31,7 @@ import { } from './helpers/chain-helpers'; import { IndexerClient } from './indexer-client'; import { UserError } from './lib/errors'; +import { generateRegistry } from './lib/registry'; import LocalWallet from './modules/local-wallet'; import { SubaccountInfo } from './subaccount'; import { ValidatorClient } from './validator-client'; @@ -498,7 +499,7 @@ export class CompositeClient { ); } - private async retrieveMarketInfo(marketId: string, marketInfo?:MarketInfo): Promise { + private async retrieveMarketInfo(marketId: string, marketInfo?: MarketInfo): Promise { if (marketInfo) { return Promise.resolve(marketInfo); } else { @@ -1000,4 +1001,98 @@ export class CompositeClient { return Buffer.from(signature).toString('base64'); } + + /** + * @description Submit a governance proposal to add a new market. + * + * @param params Parameters neeeded to create a new market. + * @param title Title of the gov proposal. + * @param summary Summary of the gov proposal. + * @param initialDepositAmount Initial deposit amount of the gov proposal. + * @param proposer proposer of the gov proposal. + * + * @returns the transaction hash. + */ + async submitGovAddNewMarketProposal( + wallet: LocalWallet, + params: GovAddNewMarketParams, + title: string, + summary: string, + initialDepositAmount: number, + ): Promise { + const msg: Promise = new Promise((resolve) => { + const composer = this.validatorClient.post.composer; + const registry = generateRegistry(); + const msgs: EncodeObject[] = []; + + // x/prices.MsgCreateOracleMarket + const createOracleMarket = composer.composeMsgCreateOracleMarket( + params.id, + params.ticker, + params.priceExponent, + params.minExchanges, + params.minPriceChange, + params.exchangeConfigJson, + ); + + // x/perpetuals.MsgCreatePerpetual + const createPerpetual = composer.composeMsgCreatePerpetual( + params.id, + params.id, + params.ticker, + params.atomicResolution, + params.liquidityTier, + ); + + // x/clob.MsgCreateClobPair + const createClobPair = composer.composeMsgCreateClobPair( + params.id, + params.id, + params.quantumConversionExponent, + params.stepBaseQuantums, + params.subticksPerTick, + ); + + // x/clob.MsgUpdateClobPair + const updateClobPair = composer.composeMsgUpdateClobPair( + params.id, + params.id, + params.quantumConversionExponent, + params.stepBaseQuantums, + params.subticksPerTick, + ); + + // x/delaymsg.MsgDelayMessage + const delayMessage = composer.composeMsgDelayMessage( + // IMPORTANT: must wrap messages in Any type to fit into delaymsg. + composer.wrapMessageAsAny(registry, updateClobPair), + params.delayBlocks, + ); + + // The order matters. + msgs.push(createOracleMarket); + msgs.push(createPerpetual); + msgs.push(createClobPair); + msgs.push(delayMessage); + + // x/gov.v1.MsgSubmitProposal + const submitProposal = composer.composeMsgSubmitProposal( + title, + initialDepositAmount, + this.validatorClient.config.denoms, // use the client denom. + summary, + // IMPORTANT: must wrap messages in Any type for gov's submit proposal. + composer.wrapMessageArrAsAny(registry, msgs), + wallet.address!, // proposer + ); + + resolve([submitProposal]); + }); + + return this.send( + wallet, + () => msg, + false, + ); + } } diff --git a/v4-client-js/src/clients/modules/composer.ts b/v4-client-js/src/clients/modules/composer.ts index eb2c35fb..8f7fdd09 100644 --- a/v4-client-js/src/clients/modules/composer.ts +++ b/v4-client-js/src/clients/modules/composer.ts @@ -341,7 +341,6 @@ export class Composer { marketId: number, ticker: string, atomicResolution: number, - defaultFundingPpm: number, liquidityTier: number, ): EncodeObject { const msg: MsgCreatePerpetual = { @@ -352,7 +351,7 @@ export class Composer { marketId, ticker, atomicResolution, - defaultFundingPpm, + defaultFundingPpm: 0, // default funding should be 0 to start. liquidityTier, }, }; diff --git a/v4-client-js/src/clients/modules/get.ts b/v4-client-js/src/clients/modules/get.ts index b35bedf7..c95b9509 100644 --- a/v4-client-js/src/clients/modules/get.ts +++ b/v4-client-js/src/clients/modules/get.ts @@ -19,8 +19,10 @@ import { BridgeModule, ClobModule, FeeTierModule, + GovV1Module, PerpetualsModule, PricesModule, + ProposalStatus, RewardsModule, StakingModule, StatsModule, @@ -365,7 +367,7 @@ export class Get { */ async getEquityTierLimitConfiguration(): Promise< ClobModule.QueryEquityTierLimitConfigurationResponse - > { + > { const requestData: Uint8Array = Uint8Array.from( ClobModule.QueryEquityTierLimitConfigurationRequest.encode({}) .finish(), @@ -470,9 +472,40 @@ export class Get { return StakingModule.QueryValidatorsResponse.decode(data); } + /** + * @description Get all gov proposals. + * + * @param proposalStatus Status of the proposal to filter by. + * @param voter Voter to filter by. + * @param depositor Depositor to filter by. + * + * @returns All gov proposals that match the filters above. + */ + async getAllGovProposals( + proposalStatus: ProposalStatus = ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD, + voter: string = '', + depositor: string = '', + ): Promise { + const requestData = Uint8Array.from( + GovV1Module.QueryProposalsRequest + .encode({ + proposalStatus, + voter, + depositor, + pagination: PAGE_REQUEST, + }) + .finish(), + ); + const data: Uint8Array = await this.sendQuery( + '/cosmos.gov.v1.Query/Proposals', + requestData, + ); + return GovV1Module.QueryProposalsResponse.decode(data); + } + private async sendQuery(requestUrl: string, requestData: Uint8Array): Promise { - const resp: QueryAbciResponse = await - this.stargateQueryClient.queryAbci(requestUrl, requestData); + // eslint-disable-next-line max-len + const resp: QueryAbciResponse = await this.stargateQueryClient.queryAbci(requestUrl, requestData); return resp.value; } } diff --git a/v4-client-js/src/clients/modules/proto-includes.ts b/v4-client-js/src/clients/modules/proto-includes.ts index bc5ce1c3..45884f36 100644 --- a/v4-client-js/src/clients/modules/proto-includes.ts +++ b/v4-client-js/src/clients/modules/proto-includes.ts @@ -1,15 +1,17 @@ +export * as GovV1Module from '@dydxprotocol/v4-proto/src/codegen/cosmos/gov/v1/query'; +export * as StatsModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/stats/query'; + export * as ClobModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/query'; export * as PerpetualsModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/perpetuals/query'; export * as PricesModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/prices/query'; -export * as SubaccountsModule -from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/subaccounts/query'; +export * as SubaccountsModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/subaccounts/query'; export * as FeeTierModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/feetiers/query'; -export * as StatsModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/stats/query'; export * as RewardsModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/rewards/query'; export * as StakingModule from '@dydxprotocol/v4-proto/src/codegen/cosmos/staking/v1beta1/query'; export * as BridgeModule from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/bridge/query'; export * from '@dydxprotocol/v4-proto/src/codegen/cosmos/base/abci/v1beta1/abci'; +export * from '@dydxprotocol/v4-proto/src/codegen/cosmos/gov/v1/gov'; export * from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/order'; export * from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/tx'; export * from '@dydxprotocol/v4-proto/src/codegen/google/protobuf/any'; diff --git a/v4-client-js/src/clients/types.ts b/v4-client-js/src/clients/types.ts index 99c6f5b0..eed86add 100644 --- a/v4-client-js/src/clients/types.ts +++ b/v4-client-js/src/clients/types.ts @@ -96,6 +96,7 @@ export interface ComplianceResponse { restricted: boolean; } +// ------------ Squid ------------ // export type SquidIBCPayload = { msgTypeUrl: '/ibc.applications.transfer.v1.MsgTransfer'; msg: Partial<{ @@ -109,4 +110,29 @@ export type SquidIBCPayload = { }>; }; +// ------------ x/gov: Add New Market ------------ // +export type GovAddNewMarketParams = { + // common + id: number; + ticker: string; + + // x/prices + priceExponent: number; + minPriceChange: number; + minExchanges: number; + exchangeConfigJson: string; + + // x/perpetuals + liquidityTier: number; + atomicResolution: number; + + // x/clob + quantumConversionExponent: number; + stepBaseQuantums: Long; + subticksPerTick: number; + + // x/delaymsg + delayBlocks: number; +}; + export * from './modules/proto-includes'; diff --git a/v4-client-js/src/lib/utils.ts b/v4-client-js/src/lib/utils.ts index 18c3ec83..5941f6ab 100644 --- a/v4-client-js/src/lib/utils.ts +++ b/v4-client-js/src/lib/utils.ts @@ -43,3 +43,27 @@ export function clientIdFromString( export async function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } + +/** + * Returns a title to use for a gov proposal that adds a new market. + * + * @param ticker ticker symbol for the new market. + * @returns title for the gov proposal. + */ +export function getGovAddNewMarketTitle(ticker: string): string { + return `Add ${ticker} perpetual market`; +} + +/** + * Returns a summary to use for a gov proposal that adds a new market. + * + * @param ticker ticker symbol for the new market. + * @param delayBlocks number of blocks to wait before activating the market. + * @returns summary for the gov proposal. + */ +export function getGovAddNewMarketSummary( + ticker: string, + delayBlocks: number, +): string { + return `Add the x/prices, x/perpetuals and x/clob parameters needed for a ${ticker} perpetual market. Create the market in INITIALIZING status and transition it to ACTIVE status after ${delayBlocks} blocks.`; +}