diff --git a/.gitmodules b/.gitmodules index be576ce..7d1379e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "thirdparty/openbook-v2"] path = thirdparty/openbook-v2 url = https://github.com/openbook-dex/openbook-v2.git + branch = master diff --git a/configure/anchor_utils.ts b/configure/anchor_utils.ts index bdb3733..16655f5 100644 --- a/configure/anchor_utils.ts +++ b/configure/anchor_utils.ts @@ -2,23 +2,31 @@ import { AnchorProvider, Provider } from "@project-serum/anchor"; import { SuccessfulTxSimulationResponse } from "@project-serum/anchor/dist/cjs/utils/rpc"; import { Connection, PublicKey, Transaction, Signer, SendOptions, ConfirmOptions, Commitment, Keypair } from "@solana/web3.js"; -export function getProviderFromKeypair(connection: Connection, authority: Keypair) : AnchorProvider { +export class TestProvider extends AnchorProvider { + keypair: Keypair; + constructor(connection: Connection, keypair: Keypair) { let txSigner = async (tx: Transaction) => { - tx.partialSign(authority); - return tx - }; - - let allSigner = async (txs : Transaction[]) => { - txs.forEach(x=> x.partialSign(authority)); - return txs; - }; - - return new AnchorProvider(connection, - { - signTransaction: txSigner, - signAllTransactions: allSigner, - publicKey : authority.publicKey, - }, - {commitment: 'confirmed'} - ) + tx.partialSign(this.keypair); + return tx + }; + + let allSigner = async (txs : Transaction[]) => { + txs.forEach(x=> x.partialSign(this.keypair)); + return txs; + }; + + super( + connection, + { + signTransaction: txSigner, + signAllTransactions: allSigner, + publicKey : keypair.publicKey, + }, + {commitment: 'confirmed'} + ) + this.keypair = keypair; + } + getKeypair() : Keypair { + return this.keypair + } } \ No newline at end of file diff --git a/configure/configure_all.ts b/configure/configure_all.ts index fcf7233..5a0530c 100644 --- a/configure/configure_all.ts +++ b/configure/configure_all.ts @@ -1,4 +1,4 @@ -import { command, number, option, string, run } from 'cmd-ts'; +import { command, number, option, string, run, boolean, flag } from 'cmd-ts'; import * as fs from 'fs'; @@ -6,16 +6,15 @@ import programs from './programs.json'; import { Commitment, Connection, Keypair, LAMPORTS_PER_SOL, Transaction } from '@solana/web3.js'; import { getKeypairFromFile } from './common_utils'; import { deploy_programs } from './deploy_programs'; -import { createPayer } from './general/create_payers'; +import { User, createUser, mintUser } from './general/create_users'; import { configure_accounts } from './general/accounts'; import { OutputFile } from './output_file'; -import { getProviderFromKeypair } from './anchor_utils'; import { MintUtils } from './general/mint_utils'; -import { configureOpenbookV2 } from './openbook-v2/configure_openbook'; +import { OpenbookConfigurator } from './openbook-v2/configure_openbook'; const numberOfAccountsToBeCreated = option({ type: number, - defaultValue: () => 1024, + defaultValue: () => 256, long: 'number-of-accounts', }); @@ -58,6 +57,14 @@ const nbMints = option({ description: "Number of mints" }); +const skipProgramDeployment = flag({ + type: boolean, + defaultValue: () => false, + long: 'skip-program-deployment', + short: 's', + description: "Skip deploying programs" + }); + const outFile = option({ type: string, defaultValue: () => "config.json", @@ -75,6 +82,7 @@ const app = command( nbPayers, balancePerPayer, nbMints, + skipProgramDeployment, outFile, }, handler: ({ @@ -84,6 +92,7 @@ const app = command( nbPayers, balancePerPayer, nbMints, + skipProgramDeployment, outFile, }) => { console.log("configuring a new test instance"); @@ -94,6 +103,7 @@ const app = command( nbPayers, balancePerPayer, nbMints, + skipProgramDeployment, outFile, ).then(_ => { console.log("configuration finished"); @@ -112,6 +122,7 @@ async function configure( nbPayers: number, balancePerPayer: number, nbMints: number, + skipProgramDeployment: boolean, outFile: String, ) { // create connections @@ -140,18 +151,11 @@ async function configure( let programIds = programOutputData.map(x => { return x.program_id }); - - console.log("starting program deployment"); - await deploy_programs(endpoint, authorityFile.toString(), programs); - console.log("programs deployed"); - - console.log("Creating payers"); - let payers = await Promise.all(Array.from(Array(nbPayers).keys()).map(_ => createPayer(connection, authority, balancePerPayer))); - console.log("Payers created"); - - console.log("Creating accounts") - let accounts = await configure_accounts(connection, authority, numberOfAccountsToBeCreated, programIds); - console.log("Accounts created") + if (!skipProgramDeployment) { + console.log("starting program deployment"); + await deploy_programs(endpoint, authorityFile.toString(), programs); + console.log("programs deployed"); + } console.log("Creating Mints"); let mintUtils = new MintUtils(connection, authority); @@ -161,14 +165,52 @@ async function configure( console.log("Configuring openbook-v2") let index = programs.findIndex(x => x.name === "openbook_v2"); let openbookProgramId = programOutputData[index].program_id; - await configureOpenbookV2(connection, authority, mintUtils, mints, openbookProgramId); + let openbookConfigurator = new OpenbookConfigurator(connection, authority, mintUtils, openbookProgramId); + let markets = await openbookConfigurator.configureOpenbookV2(mints); console.log("Finished configuring openbook") + console.log("Creating users"); + let users = await Promise.all(Array.from(Array(nbPayers).keys()).map(_ => createUser(connection, authority, balancePerPayer))); + let tokenAccounts = await Promise.all(users.map( + /// user is richer than bill gates, but not as rich as certain world leaders + user => mintUser(connection, authority, mints, mintUtils, user.publicKey, 100_000_000_000) + )) + + let userOpenOrders = await Promise.all(users.map( + /// user is crazy betting all his money in crypto market + user => openbookConfigurator.configureMarketForUser(user, markets, 100_000_000_000) + )) + + let userData: User [] = users.map((user, i) => { + return { + keypair: user, + openOrders: userOpenOrders[i], + token: tokenAccounts[i], + } + }) + + console.log("Users created"); + + console.log("Creating accounts") + let accounts = await configure_accounts(connection, authority, numberOfAccountsToBeCreated, programIds); + + // adding known accounts + const marketAccountsList = markets.map(market => [market.asks, market.bids, market.marketPk, market.oracle, market.quoteVault, market.baseVault, market.baseMint, market.quoteMint] ).flat(); + const userAccountsList = userData.map(user => { + const allOpenOrdersAccounts = user.openOrders.map(x=>x.openOrders).flat(); + const allTokenAccounts = user.token.map(x => x.tokenAccount); + return allOpenOrdersAccounts.concat(allTokenAccounts) + }).flat() + accounts = accounts.concat(marketAccountsList).concat(userAccountsList); + + console.log("Accounts created") + let outputFile: OutputFile = { programs: programOutputData, known_accounts: accounts, - payers: payers.map(x => Array.from(x.secretKey)), - mints: mints, + users: userData, + mints, + markets, } console.log("creating output file") diff --git a/configure/general/create_payers.ts b/configure/general/create_payers.ts deleted file mode 100644 index f16a49e..0000000 --- a/configure/general/create_payers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Connection, Keypair, LAMPORTS_PER_SOL, SystemProgram, Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; - -export async function createPayer(connection: Connection, authority: Keypair, balancePerPayer: number): Promise { - let payer = Keypair.generate(); - let transfer_ix = SystemProgram.transfer({ - fromPubkey: authority.publicKey, - toPubkey: payer.publicKey, - lamports: balancePerPayer * LAMPORTS_PER_SOL, - }); - let tx = new Transaction().add(transfer_ix); - tx.feePayer = authority.publicKey; - const bh = await connection.getLatestBlockhash(); - tx.recentBlockhash = bh.blockhash; - sendAndConfirmTransaction(connection, tx, [authority]); - return payer -} diff --git a/configure/general/create_users.ts b/configure/general/create_users.ts new file mode 100644 index 0000000..54bf00e --- /dev/null +++ b/configure/general/create_users.ts @@ -0,0 +1,44 @@ +import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; +import { Market } from "../openbook-v2/create_markets"; +import { MintUtils } from "./mint_utils"; +import * as splToken from '@solana/spl-token' +import { OpenOrders } from "../openbook-v2/configure_openbook"; + +export interface User { + keypair: Keypair, + token : TokenAccountData[], + openOrders: OpenOrders[], +} + +export async function createUser(connection: Connection, authority: Keypair, balancePerPayer: number): Promise { + let payer = Keypair.generate(); + let transfer_ix = SystemProgram.transfer({ + fromPubkey: authority.publicKey, + toPubkey: payer.publicKey, + lamports: balancePerPayer * LAMPORTS_PER_SOL, + }); + let tx = new Transaction().add(transfer_ix); + tx.feePayer = authority.publicKey; + const bh = await connection.getLatestBlockhash(); + tx.recentBlockhash = bh.blockhash; + sendAndConfirmTransaction(connection, tx, [authority]); + return payer +} + +interface TokenAccountData { + mint: PublicKey, + tokenAccount: PublicKey +} + +export async function mintUser(connection: Connection, authority: Keypair, mints: PublicKey[], mintUtils: MintUtils, user: PublicKey, amount: number) : Promise { + return await Promise.all( + mints.map(async(mint)=> { + const tokenAccount = await mintUtils.createTokenAccount(mint, authority, user); + await splToken.mintTo(connection, authority, mint, tokenAccount, authority, amount); + return { + mint: mint, + tokenAccount: tokenAccount + } + }) + ) +} diff --git a/configure/openbook-v2/configure_openbook.ts b/configure/openbook-v2/configure_openbook.ts index 5e0f3d3..5b2bd79 100644 --- a/configure/openbook-v2/configure_openbook.ts +++ b/configure/openbook-v2/configure_openbook.ts @@ -1,12 +1,65 @@ import { Connection, Keypair, PublicKey } from "@solana/web3.js"; -import { getProviderFromKeypair } from "../anchor_utils"; +import { TestProvider } from "../anchor_utils"; import { Market, createMarket } from "./create_markets"; import { MintUtils } from "../general/mint_utils"; +import { OpenbookV2 } from "./openbook_v2"; +import IDL from '../programs/openbook_v2.json' +import { BN, Program, web3 } from "@project-serum/anchor"; -export async function configureOpenbookV2(connection: Connection, authority: Keypair, mintUtils: MintUtils, mints: PublicKey[], openbookProgramId: PublicKey): Promise { - - let anchorProvider = getProviderFromKeypair(connection, authority); - let quoteMint = mints[0]; - let admin = Keypair.generate(); - return await Promise.all(mints.slice(1).map((mint, index) => createMarket(anchorProvider, mintUtils, admin, openbookProgramId, mint, quoteMint, authority, index))) +export interface OpenOrders { + market: PublicKey, + openOrders: PublicKey +} + +export class OpenbookConfigurator { + + anchorProvider: TestProvider; + mintUtils: MintUtils; + openbookProgramId: PublicKey; + + constructor(connection: Connection, authority: Keypair, mintUtils: MintUtils, openbookProgramId: PublicKey) { + this.anchorProvider = new TestProvider(connection, authority); + this.mintUtils = mintUtils; + this.openbookProgramId = openbookProgramId; + } + + public async configureOpenbookV2(mints: PublicKey[]): Promise { + let quoteMint = mints[0]; + let admin = Keypair.generate(); + return await Promise.all(mints.slice(1).map((mint, index) => createMarket(this.anchorProvider, this.mintUtils, admin, this.openbookProgramId, mint, quoteMint, index))) + } + + public async configureMarketForUser(user: Keypair, markets: Market[], depositAmount: number) : Promise { + let program = new Program( + IDL as OpenbookV2, + this.openbookProgramId, + this.anchorProvider, + ); + + const openOrders = await Promise.all( + markets.map(async(market) => { + let accountIndex = new BN(0); + let [openOrders, _tmp] = PublicKey.findProgramAddressSync([Buffer.from("OpenOrders"), user.publicKey.toBuffer(), market.marketPk.toBuffer(), accountIndex.toBuffer("le", 4)], this.openbookProgramId) + + await program.methods.initOpenOrders( + 0, + 64 + ).accounts({ + openOrdersAccount: openOrders, + market: market.marketPk, + owner: user.publicKey, + payer: this.anchorProvider.publicKey, + systemProgram: web3.SystemProgram.programId, + }).signers([user]).rpc(); + return [market.marketPk, openOrders] + }) + ) + + return openOrders.map(x=> { + return { + market : x[0], + openOrders : x[1], + } + }) + } } \ No newline at end of file diff --git a/configure/openbook-v2/create_markets.ts b/configure/openbook-v2/create_markets.ts index 5c824cf..3143de5 100644 --- a/configure/openbook-v2/create_markets.ts +++ b/configure/openbook-v2/create_markets.ts @@ -1,10 +1,11 @@ import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import IDL from '../programs/openbook_v2.json' -import { AnchorProvider, Idl, Program, web3, BN } from '@project-serum/anchor'; +import { Program, web3, BN } from '@project-serum/anchor'; import { createAccount } from '../general/solana_utils'; import { MintUtils } from '../general/mint_utils'; import { I80F48, I80F48Dto } from '@blockworks-foundation/mango-v4'; import { OpenbookV2 } from './openbook_v2'; +import { TestProvider } from '../anchor_utils'; export interface Market { name: string, @@ -21,7 +22,7 @@ export interface Market { marketIndex: number, } -export async function createMarket(anchorProvider: AnchorProvider, mintUtils: MintUtils, adminKp: Keypair, openbookProgramId: PublicKey, baseMint: PublicKey, quoteMint: PublicKey, payer: Keypair, index: number): Promise { +export async function createMarket(anchorProvider: TestProvider, mintUtils: MintUtils, adminKp: Keypair, openbookProgramId: PublicKey, baseMint: PublicKey, quoteMint: PublicKey, index: number): Promise { let program = new Program( IDL as OpenbookV2, openbookProgramId, @@ -35,23 +36,23 @@ export async function createMarket(anchorProvider: AnchorProvider, mintUtils: Mi oracle: oracleId, admin, mint: baseMint, - payer: payer.publicKey, + payer: anchorProvider.wallet.publicKey, systemProgram: web3.SystemProgram.programId, }) - .signers([adminKp, payer]) + .signers([adminKp]) .rpc(); // bookside size = 123720 - let asks = await createAccount(anchorProvider.connection, payer, 123720, openbookProgramId); - let bids = await createAccount(anchorProvider.connection, payer, 123720, openbookProgramId); - let eventQueue = await createAccount(anchorProvider.connection, payer, 97688, openbookProgramId); + let asks = await createAccount(anchorProvider.connection, anchorProvider.keypair, 123720, openbookProgramId); + let bids = await createAccount(anchorProvider.connection, anchorProvider.keypair, 123720, openbookProgramId); + let eventQueue = await createAccount(anchorProvider.connection, anchorProvider.keypair, 97688, openbookProgramId); let marketIndex : BN = new BN(index); - let [marketPk, _tmp2] = PublicKey.findProgramAddressSync([Buffer.from("Market"), marketIndex.toBuffer("le", 4)], openbookProgramId) + let [marketPk, _tmp2] = PublicKey.findProgramAddressSync([Buffer.from("Market"), admin.toBuffer(), marketIndex.toBuffer("le", 4)], openbookProgramId) - let baseVault = await mintUtils.createTokenAccount(baseMint, payer, marketPk); - let quoteVault = await mintUtils.createTokenAccount(quoteMint, payer, marketPk); - let name = 'token at index ' + index.toString() + ' wrt at index 0'; + let baseVault = await mintUtils.createTokenAccount(baseMint, anchorProvider.keypair, marketPk); + let quoteVault = await mintUtils.createTokenAccount(quoteMint, anchorProvider.keypair, marketPk); + let name = 'index ' + index.toString() + ' wrt 0'; await program.methods.createMarket( marketIndex, @@ -68,7 +69,7 @@ export async function createMarket(anchorProvider: AnchorProvider, mintUtils: Mi bids, asks, eventQueue, - payer: payer.publicKey, + payer: anchorProvider.publicKey, baseVault, quoteVault, baseMint, @@ -76,7 +77,7 @@ export async function createMarket(anchorProvider: AnchorProvider, mintUtils: Mi systemProgram: web3.SystemProgram.programId, oracle: oracleId, } - ).signers([adminKp, payer]) + ).signers([adminKp]) .rpc(); return { diff --git a/configure/openbook-v2/openbook_v2.ts b/configure/openbook-v2/openbook_v2.ts index c2f84e9..55f1b9a 100644 --- a/configure/openbook-v2/openbook_v2.ts +++ b/configure/openbook-v2/openbook_v2.ts @@ -4,6 +4,9 @@ export type OpenbookV2 = { "instructions": [ { "name": "createMarket", + "docs": [ + "Create a [`Market`](crate::state::Market) for a given token pair." + ], "accounts": [ { "name": "admin", @@ -149,6 +152,24 @@ export type OpenbookV2 = { }, { "name": "placeOrder", + "docs": [ + "Place an order.", + "", + "Different types of orders have different effects on the order book,", + "as described in [`PlaceOrderType`](crate::state::PlaceOrderType).", + "", + "`price_lots` refers to the price in lots: the number of quote lots", + "per base lot. It is ignored for `PlaceOrderType::Market` orders.", + "", + "`expiry_timestamp` is a unix timestamp for when this order should", + "expire. If 0 is passed in, the order will never expire. If the time", + "is in the past, the instruction is skipped. Timestamps in the future", + "are reduced to now + 65,535s.", + "", + "`limit` determines the maximum number of orders from the book to fill,", + "and can be used to limit CU spent. When the limit is reached, processing", + "stops and the instruction succeeds." + ], "accounts": [ { "name": "openOrdersAccount", @@ -377,6 +398,12 @@ export type OpenbookV2 = { }, { "name": "placeTakeOrder", + "docs": [ + "Place an order that shall take existing liquidity off of the book, not", + "add a new order off the book.", + "", + "This type of order allows for instant token settlement for the taker." + ], "accounts": [ { "name": "owner", @@ -483,6 +510,31 @@ export type OpenbookV2 = { }, { "name": "consumeEvents", + "docs": [ + "Process up to `limit` [events](crate::state::AnyEvent).", + "", + "When a user places a 'take' order, they do not know beforehand which", + "market maker will have placed the 'make' order that they get executed", + "against. This prevents them from passing in a market maker's", + "[`OpenOrdersAccount`](crate::state::OpenOrdersAccount), which is needed", + "to credit/debit the relevant tokens to/from the maker. As such, Openbook", + "uses a 'crank' system, where `place_order` only emits events, and", + "`consume_events` handles token settlement.", + "", + "Currently, there are two types of events: [`FillEvent`](crate::state::FillEvent)s", + "and [`OutEvent`](crate::state::OutEvent)s.", + "", + "A `FillEvent` is emitted when an order is filled, and it is handled by", + "debiting whatever the taker is selling from the taker and crediting", + "it to the maker, and debiting whatever the taker is buying from the", + "maker and crediting it to the taker. Note that *no tokens are moved*,", + "these are just debits and credits to each party's [`Position`](crate::state::Position).", + "", + "An `OutEvent` is emitted when a limit order needs to be removed from", + "the book during a `place_order` invocation, and it is handled by", + "crediting whatever the maker would have sold (quote token in a bid,", + "base token in an ask) back to the maker." + ], "accounts": [ { "name": "market", @@ -504,6 +556,12 @@ export type OpenbookV2 = { }, { "name": "cancelOrder", + "docs": [ + "Cancel an order by its `order_id`.", + "", + "Note that this doesn't emit an [`OutEvent`](crate::state::OutEvent) because a", + "maker knows that they will be passing in their own [`OpenOrdersAccount`](crate::state::OpenOrdersAccount)." + ], "accounts": [ { "name": "openOrdersAccount", @@ -540,6 +598,12 @@ export type OpenbookV2 = { }, { "name": "cancelOrderByClientOrderId", + "docs": [ + "Cancel an order by its `client_order_id`.", + "", + "Note that this doesn't emit an [`OutEvent`](crate::state::OutEvent) because a", + "maker knows that they will be passing in their own [`OpenOrdersAccount`](crate::state::OpenOrdersAccount)." + ], "accounts": [ { "name": "openOrdersAccount", @@ -576,6 +640,9 @@ export type OpenbookV2 = { }, { "name": "cancelAllOrders", + "docs": [ + "Cancel up to `limit` orders." + ], "accounts": [ { "name": "openOrdersAccount", @@ -612,6 +679,9 @@ export type OpenbookV2 = { }, { "name": "cancelAllOrdersBySide", + "docs": [ + "Cancel up to `limit` orders on a single side of the book." + ], "accounts": [ { "name": "openOrdersAccount", @@ -656,6 +726,13 @@ export type OpenbookV2 = { }, { "name": "deposit", + "docs": [ + "Desposit a certain amount of `base_amount_lots` and `quote_amount_lots`", + "into one's [`Position`](crate::state::Position).", + "", + "Makers might wish to `deposit`, rather than have actual tokens moved for", + "each trade, in order to reduce CUs." + ], "accounts": [ { "name": "owner", @@ -716,6 +793,9 @@ export type OpenbookV2 = { }, { "name": "settleFunds", + "docs": [ + "Withdraw any available tokens." + ], "accounts": [ { "name": "openOrdersAccount", @@ -762,6 +842,9 @@ export type OpenbookV2 = { }, { "name": "sweepFees", + "docs": [ + "Sweep fees, as a [`Market`](crate::state::Market)'s admin." + ], "accounts": [ { "name": "market", @@ -793,6 +876,9 @@ export type OpenbookV2 = { }, { "name": "closeMarket", + "docs": [ + "Close a [`Market`](crate::state::Market)." + ], "accounts": [ { "name": "admin", @@ -2877,6 +2963,9 @@ export const IDL: OpenbookV2 = { "instructions": [ { "name": "createMarket", + "docs": [ + "Create a [`Market`](crate::state::Market) for a given token pair." + ], "accounts": [ { "name": "admin", @@ -3022,6 +3111,24 @@ export const IDL: OpenbookV2 = { }, { "name": "placeOrder", + "docs": [ + "Place an order.", + "", + "Different types of orders have different effects on the order book,", + "as described in [`PlaceOrderType`](crate::state::PlaceOrderType).", + "", + "`price_lots` refers to the price in lots: the number of quote lots", + "per base lot. It is ignored for `PlaceOrderType::Market` orders.", + "", + "`expiry_timestamp` is a unix timestamp for when this order should", + "expire. If 0 is passed in, the order will never expire. If the time", + "is in the past, the instruction is skipped. Timestamps in the future", + "are reduced to now + 65,535s.", + "", + "`limit` determines the maximum number of orders from the book to fill,", + "and can be used to limit CU spent. When the limit is reached, processing", + "stops and the instruction succeeds." + ], "accounts": [ { "name": "openOrdersAccount", @@ -3250,6 +3357,12 @@ export const IDL: OpenbookV2 = { }, { "name": "placeTakeOrder", + "docs": [ + "Place an order that shall take existing liquidity off of the book, not", + "add a new order off the book.", + "", + "This type of order allows for instant token settlement for the taker." + ], "accounts": [ { "name": "owner", @@ -3356,6 +3469,31 @@ export const IDL: OpenbookV2 = { }, { "name": "consumeEvents", + "docs": [ + "Process up to `limit` [events](crate::state::AnyEvent).", + "", + "When a user places a 'take' order, they do not know beforehand which", + "market maker will have placed the 'make' order that they get executed", + "against. This prevents them from passing in a market maker's", + "[`OpenOrdersAccount`](crate::state::OpenOrdersAccount), which is needed", + "to credit/debit the relevant tokens to/from the maker. As such, Openbook", + "uses a 'crank' system, where `place_order` only emits events, and", + "`consume_events` handles token settlement.", + "", + "Currently, there are two types of events: [`FillEvent`](crate::state::FillEvent)s", + "and [`OutEvent`](crate::state::OutEvent)s.", + "", + "A `FillEvent` is emitted when an order is filled, and it is handled by", + "debiting whatever the taker is selling from the taker and crediting", + "it to the maker, and debiting whatever the taker is buying from the", + "maker and crediting it to the taker. Note that *no tokens are moved*,", + "these are just debits and credits to each party's [`Position`](crate::state::Position).", + "", + "An `OutEvent` is emitted when a limit order needs to be removed from", + "the book during a `place_order` invocation, and it is handled by", + "crediting whatever the maker would have sold (quote token in a bid,", + "base token in an ask) back to the maker." + ], "accounts": [ { "name": "market", @@ -3377,6 +3515,12 @@ export const IDL: OpenbookV2 = { }, { "name": "cancelOrder", + "docs": [ + "Cancel an order by its `order_id`.", + "", + "Note that this doesn't emit an [`OutEvent`](crate::state::OutEvent) because a", + "maker knows that they will be passing in their own [`OpenOrdersAccount`](crate::state::OpenOrdersAccount)." + ], "accounts": [ { "name": "openOrdersAccount", @@ -3413,6 +3557,12 @@ export const IDL: OpenbookV2 = { }, { "name": "cancelOrderByClientOrderId", + "docs": [ + "Cancel an order by its `client_order_id`.", + "", + "Note that this doesn't emit an [`OutEvent`](crate::state::OutEvent) because a", + "maker knows that they will be passing in their own [`OpenOrdersAccount`](crate::state::OpenOrdersAccount)." + ], "accounts": [ { "name": "openOrdersAccount", @@ -3449,6 +3599,9 @@ export const IDL: OpenbookV2 = { }, { "name": "cancelAllOrders", + "docs": [ + "Cancel up to `limit` orders." + ], "accounts": [ { "name": "openOrdersAccount", @@ -3485,6 +3638,9 @@ export const IDL: OpenbookV2 = { }, { "name": "cancelAllOrdersBySide", + "docs": [ + "Cancel up to `limit` orders on a single side of the book." + ], "accounts": [ { "name": "openOrdersAccount", @@ -3529,6 +3685,13 @@ export const IDL: OpenbookV2 = { }, { "name": "deposit", + "docs": [ + "Desposit a certain amount of `base_amount_lots` and `quote_amount_lots`", + "into one's [`Position`](crate::state::Position).", + "", + "Makers might wish to `deposit`, rather than have actual tokens moved for", + "each trade, in order to reduce CUs." + ], "accounts": [ { "name": "owner", @@ -3589,6 +3752,9 @@ export const IDL: OpenbookV2 = { }, { "name": "settleFunds", + "docs": [ + "Withdraw any available tokens." + ], "accounts": [ { "name": "openOrdersAccount", @@ -3635,6 +3801,9 @@ export const IDL: OpenbookV2 = { }, { "name": "sweepFees", + "docs": [ + "Sweep fees, as a [`Market`](crate::state::Market)'s admin." + ], "accounts": [ { "name": "market", @@ -3666,6 +3835,9 @@ export const IDL: OpenbookV2 = { }, { "name": "closeMarket", + "docs": [ + "Close a [`Market`](crate::state::Market)." + ], "accounts": [ { "name": "admin", diff --git a/configure/output_file.ts b/configure/output_file.ts index c4c0635..1df6deb 100644 --- a/configure/output_file.ts +++ b/configure/output_file.ts @@ -1,4 +1,6 @@ import { PublicKey } from "@solana/web3.js"; +import { Market } from "./openbook-v2/create_markets"; +import { User } from "./general/create_users"; export interface ProgramOutputData { name: String, @@ -6,8 +8,9 @@ export interface ProgramOutputData { } export interface OutputFile { - payers: number[][], + users: User[], programs: ProgramOutputData[], known_accounts: PublicKey[], mints: PublicKey[], + markets: Market[], } diff --git a/configure/programs/openbook_v2.json b/configure/programs/openbook_v2.json index a6ba698..176fc80 100644 --- a/configure/programs/openbook_v2.json +++ b/configure/programs/openbook_v2.json @@ -4,6 +4,9 @@ "instructions": [ { "name": "createMarket", + "docs": [ + "Create a [`Market`](crate::state::Market) for a given token pair." + ], "accounts": [ { "name": "admin", @@ -149,6 +152,24 @@ }, { "name": "placeOrder", + "docs": [ + "Place an order.", + "", + "Different types of orders have different effects on the order book,", + "as described in [`PlaceOrderType`](crate::state::PlaceOrderType).", + "", + "`price_lots` refers to the price in lots: the number of quote lots", + "per base lot. It is ignored for `PlaceOrderType::Market` orders.", + "", + "`expiry_timestamp` is a unix timestamp for when this order should", + "expire. If 0 is passed in, the order will never expire. If the time", + "is in the past, the instruction is skipped. Timestamps in the future", + "are reduced to now + 65,535s.", + "", + "`limit` determines the maximum number of orders from the book to fill,", + "and can be used to limit CU spent. When the limit is reached, processing", + "stops and the instruction succeeds." + ], "accounts": [ { "name": "openOrdersAccount", @@ -377,6 +398,12 @@ }, { "name": "placeTakeOrder", + "docs": [ + "Place an order that shall take existing liquidity off of the book, not", + "add a new order off the book.", + "", + "This type of order allows for instant token settlement for the taker." + ], "accounts": [ { "name": "owner", @@ -483,6 +510,31 @@ }, { "name": "consumeEvents", + "docs": [ + "Process up to `limit` [events](crate::state::AnyEvent).", + "", + "When a user places a 'take' order, they do not know beforehand which", + "market maker will have placed the 'make' order that they get executed", + "against. This prevents them from passing in a market maker's", + "[`OpenOrdersAccount`](crate::state::OpenOrdersAccount), which is needed", + "to credit/debit the relevant tokens to/from the maker. As such, Openbook", + "uses a 'crank' system, where `place_order` only emits events, and", + "`consume_events` handles token settlement.", + "", + "Currently, there are two types of events: [`FillEvent`](crate::state::FillEvent)s", + "and [`OutEvent`](crate::state::OutEvent)s.", + "", + "A `FillEvent` is emitted when an order is filled, and it is handled by", + "debiting whatever the taker is selling from the taker and crediting", + "it to the maker, and debiting whatever the taker is buying from the", + "maker and crediting it to the taker. Note that *no tokens are moved*,", + "these are just debits and credits to each party's [`Position`](crate::state::Position).", + "", + "An `OutEvent` is emitted when a limit order needs to be removed from", + "the book during a `place_order` invocation, and it is handled by", + "crediting whatever the maker would have sold (quote token in a bid,", + "base token in an ask) back to the maker." + ], "accounts": [ { "name": "market", @@ -504,6 +556,12 @@ }, { "name": "cancelOrder", + "docs": [ + "Cancel an order by its `order_id`.", + "", + "Note that this doesn't emit an [`OutEvent`](crate::state::OutEvent) because a", + "maker knows that they will be passing in their own [`OpenOrdersAccount`](crate::state::OpenOrdersAccount)." + ], "accounts": [ { "name": "openOrdersAccount", @@ -540,6 +598,12 @@ }, { "name": "cancelOrderByClientOrderId", + "docs": [ + "Cancel an order by its `client_order_id`.", + "", + "Note that this doesn't emit an [`OutEvent`](crate::state::OutEvent) because a", + "maker knows that they will be passing in their own [`OpenOrdersAccount`](crate::state::OpenOrdersAccount)." + ], "accounts": [ { "name": "openOrdersAccount", @@ -576,6 +640,9 @@ }, { "name": "cancelAllOrders", + "docs": [ + "Cancel up to `limit` orders." + ], "accounts": [ { "name": "openOrdersAccount", @@ -612,6 +679,9 @@ }, { "name": "cancelAllOrdersBySide", + "docs": [ + "Cancel up to `limit` orders on a single side of the book." + ], "accounts": [ { "name": "openOrdersAccount", @@ -656,6 +726,13 @@ }, { "name": "deposit", + "docs": [ + "Desposit a certain amount of `base_amount_lots` and `quote_amount_lots`", + "into one's [`Position`](crate::state::Position).", + "", + "Makers might wish to `deposit`, rather than have actual tokens moved for", + "each trade, in order to reduce CUs." + ], "accounts": [ { "name": "owner", @@ -716,6 +793,9 @@ }, { "name": "settleFunds", + "docs": [ + "Withdraw any available tokens." + ], "accounts": [ { "name": "openOrdersAccount", @@ -762,6 +842,9 @@ }, { "name": "sweepFees", + "docs": [ + "Sweep fees, as a [`Market`](crate::state::Market)'s admin." + ], "accounts": [ { "name": "market", @@ -793,6 +876,9 @@ }, { "name": "closeMarket", + "docs": [ + "Close a [`Market`](crate::state::Market)." + ], "accounts": [ { "name": "admin", diff --git a/configure/programs/openbook_v2.so b/configure/programs/openbook_v2.so index 6dda010..7be36d0 100755 Binary files a/configure/programs/openbook_v2.so and b/configure/programs/openbook_v2.so differ diff --git a/thirdparty/openbook-v2 b/thirdparty/openbook-v2 index 3d227cc..b775417 160000 --- a/thirdparty/openbook-v2 +++ b/thirdparty/openbook-v2 @@ -1 +1 @@ -Subproject commit 3d227cce696891b2e5f6cc7ae71d94a718952c1f +Subproject commit b7754174bf8c8cbcbda25372fbd0f8dc8b429317