123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- const assert = require("assert");
- const { Token, TOKEN_PROGRAM_ID } = require("@solana/spl-token");
- const anchor = require("@project-serum/anchor");
- const serum = require("@project-serum/serum");
- const { BN } = anchor;
- const { Transaction, TransactionInstruction } = anchor.web3;
- const { DexInstructions, OpenOrders, Market } = serum;
- const { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } = anchor.web3;
- const { initMarket, sleep } = require("./utils");
- const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
- const REFERRAL = new PublicKey("2k1bb16Hu7ocviT2KC3wcCgETtnC8tEUuvFBH4C5xStG");
- describe("permissioned-markets", () => {
- // Anchor client setup.
- const provider = anchor.Provider.env();
- anchor.setProvider(provider);
- const program = anchor.workspace.PermissionedMarkets;
- // Token clients.
- let usdcClient;
- // Global DEX accounts and clients shared accross all tests.
- let marketClient, tokenAccount, usdcAccount;
- let openOrders, openOrdersBump, openOrdersInitAuthority, openOrdersBumpinit;
- let usdcPosted;
- let marketMakerOpenOrders;
- it("BOILERPLATE: Initializes an orderbook", async () => {
- const {
- marketMakerOpenOrders: mmOo,
- marketA,
- godA,
- godUsdc,
- usdc,
- } = await initMarket({ provider });
- marketClient = marketA;
- marketClient._programId = program.programId;
- usdcAccount = godUsdc;
- tokenAccount = godA;
- marketMakerOpenOrders = mmOo;
- usdcClient = new Token(
- provider.connection,
- usdc,
- TOKEN_PROGRAM_ID,
- provider.wallet.payer
- );
- });
- it("BOILERPLATE: Calculates open orders addresses", async () => {
- const [_openOrders, bump] = await PublicKey.findProgramAddress(
- [
- anchor.utils.bytes.utf8.encode("open-orders"),
- marketClient.address.toBuffer(),
- program.provider.wallet.publicKey.toBuffer(),
- ],
- program.programId
- );
- const [
- _openOrdersInitAuthority,
- bumpInit,
- ] = await PublicKey.findProgramAddress(
- [
- anchor.utils.bytes.utf8.encode("open-orders-init"),
- marketClient.address.toBuffer(),
- ],
- program.programId
- );
- // Save global variables re-used across tests.
- openOrders = _openOrders;
- openOrdersBump = bump;
- openOrdersInitAuthority = _openOrdersInitAuthority;
- openOrdersBumpInit = bumpInit;
- });
- it("Creates an open orders account", async () => {
- await program.rpc.initAccount(openOrdersBump, openOrdersBumpInit, {
- accounts: {
- openOrdersInitAuthority,
- openOrders,
- authority: program.provider.wallet.publicKey,
- market: marketClient.address,
- rent: SYSVAR_RENT_PUBKEY,
- systemProgram: SystemProgram.programId,
- dexProgram: DEX_PID,
- },
- });
- const account = await provider.connection.getAccountInfo(openOrders);
- assert.ok(account.owner.toString() === DEX_PID.toString());
- });
- it("Posts a bid on the orderbook", async () => {
- const size = 1;
- const price = 1;
- // The amount of USDC transferred into the dex for the trade.
- usdcPosted = new BN(marketClient._decoded.quoteLotSize.toNumber()).mul(
- marketClient
- .baseSizeNumberToLots(size)
- .mul(marketClient.priceNumberToLots(price))
- );
- // Note: Prepend delegate approve to the tx since the owner of the token
- // account must match the owner of the open orders account. We
- // can probably hide this in the serum client.
- const tx = new Transaction();
- tx.add(
- Token.createApproveInstruction(
- TOKEN_PROGRAM_ID,
- usdcAccount,
- openOrders,
- program.provider.wallet.publicKey,
- [],
- usdcPosted.toNumber()
- )
- );
- tx.add(
- serumProxy(
- marketClient.makePlaceOrderInstruction(program.provider.connection, {
- owner: program.provider.wallet.publicKey,
- payer: usdcAccount,
- side: "buy",
- price,
- size,
- orderType: "postOnly",
- clientId: new BN(999),
- openOrdersAddressKey: openOrders,
- selfTradeBehavior: "abortTransaction",
- })
- )
- );
- await provider.send(tx);
- });
- it("Cancels a bid on the orderbook", async () => {
- // Given.
- const beforeOoAccount = await OpenOrders.load(
- provider.connection,
- openOrders,
- DEX_PID
- );
- // When.
- const tx = new Transaction();
- tx.add(
- serumProxy(
- (
- await marketClient.makeCancelOrderByClientIdTransaction(
- program.provider.connection,
- program.provider.wallet.publicKey,
- openOrders,
- new BN(999)
- )
- ).instructions[0]
- )
- );
- await provider.send(tx);
- // Then.
- const afterOoAccount = await OpenOrders.load(
- provider.connection,
- openOrders,
- DEX_PID
- );
- assert.ok(beforeOoAccount.quoteTokenFree.eq(new BN(0)));
- assert.ok(beforeOoAccount.quoteTokenTotal.eq(usdcPosted));
- assert.ok(afterOoAccount.quoteTokenFree.eq(usdcPosted));
- assert.ok(afterOoAccount.quoteTokenTotal.eq(usdcPosted));
- });
- // Need to crank the cancel so that we can close later.
- it("Cranks the cancel transaction", async () => {
- // TODO: can do this in a single transaction if we covert the pubkey bytes
- // into a [u64; 4] array and sort. I'm lazy though.
- let eq = await marketClient.loadEventQueue(provider.connection);
- while (eq.length > 0) {
- const tx = new Transaction();
- tx.add(
- DexInstructions.consumeEvents({
- market: marketClient._decoded.ownAddress,
- eventQueue: marketClient._decoded.eventQueue,
- coinFee: marketClient._decoded.eventQueue,
- pcFee: marketClient._decoded.eventQueue,
- openOrdersAccounts: [eq[0].openOrders],
- limit: 1,
- programId: DEX_PID,
- })
- );
- await provider.send(tx);
- eq = await marketClient.loadEventQueue(provider.connection);
- }
- });
- it("Settles funds on the orderbook", async () => {
- // Given.
- const beforeTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
- // When.
- const tx = new Transaction();
- tx.add(
- serumProxy(
- DexInstructions.settleFunds({
- market: marketClient._decoded.ownAddress,
- openOrders,
- owner: provider.wallet.publicKey,
- baseVault: marketClient._decoded.baseVault,
- quoteVault: marketClient._decoded.quoteVault,
- baseWallet: tokenAccount,
- quoteWallet: usdcAccount,
- vaultSigner: await PublicKey.createProgramAddress(
- [
- marketClient.address.toBuffer(),
- marketClient._decoded.vaultSignerNonce.toArrayLike(
- Buffer,
- "le",
- 8
- ),
- ],
- DEX_PID
- ),
- programId: program.programId,
- referrerQuoteWallet: usdcAccount,
- })
- )
- );
- await provider.send(tx);
- // Then.
- const afterTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
- assert.ok(
- afterTokenAccount.amount.sub(beforeTokenAccount.amount).toNumber() ===
- usdcPosted.toNumber()
- );
- });
- it("Closes an open orders account", async () => {
- // Given.
- const beforeAccount = await program.provider.connection.getAccountInfo(
- program.provider.wallet.publicKey
- );
- // When.
- const tx = new Transaction();
- tx.add(
- serumProxy(
- DexInstructions.closeOpenOrders({
- market: marketClient._decoded.ownAddress,
- openOrders,
- owner: program.provider.wallet.publicKey,
- solWallet: program.provider.wallet.publicKey,
- programId: program.programId,
- })
- )
- );
- await provider.send(tx);
- // Then.
- const afterAccount = await program.provider.connection.getAccountInfo(
- program.provider.wallet.publicKey
- );
- const closedAccount = await program.provider.connection.getAccountInfo(
- openOrders
- );
- assert.ok(23352768 === afterAccount.lamports - beforeAccount.lamports);
- assert.ok(closedAccount === null);
- });
- });
- // Adds the serum dex account to the instruction so that proxies can
- // relay (CPI requires the executable account).
- //
- // TODO: we should add flag in the dex client that says if a proxy is being
- // used, and if so, do this automatically.
- function serumProxy(ix) {
- ix.keys = [
- { pubkey: DEX_PID, isWritable: false, isSigner: false },
- ...ix.keys,
- ];
- return ix;
- }
|