diff --git a/programs/monaco_protocol/src/context.rs b/programs/monaco_protocol/src/context.rs index ef52e576..fdec5045 100644 --- a/programs/monaco_protocol/src/context.rs +++ b/programs/monaco_protocol/src/context.rs @@ -1097,6 +1097,44 @@ pub struct VoidMarket<'info> { pub authorised_operators: Account<'info, AuthorisedOperators>, } +#[derive(Accounts)] +pub struct ForceVoidMarket<'info> { + #[account(mut)] + pub market: Account<'info, Market>, + #[account( + mut, + has_one = market @ CoreError::VoidMarketMismatch, + )] + pub market_matching_queue: Option>, + #[account( + has_one = market @ CoreError::VoidMarketMismatch, + )] + pub order_request_queue: Option>, + + #[account(mut)] + pub market_operator: Signer<'info>, + #[account(seeds = [b"authorised_operators".as_ref(), b"MARKET".as_ref()], bump)] + pub authorised_operators: Account<'info, AuthorisedOperators>, +} + +#[derive(Accounts)] +pub struct ForceUnsettledCount<'info> { + #[account(mut)] + pub market: Account<'info, Market>, + #[account( + token::mint = market.mint_account, + token::authority = market_escrow, + seeds = [b"escrow".as_ref(), market.key().as_ref()], + bump, + )] + pub market_escrow: Account<'info, TokenAccount>, + + #[account(mut)] + pub market_operator: Signer<'info>, + #[account(seeds = [b"authorised_operators".as_ref(), b"MARKET".as_ref()], bump)] + pub authorised_operators: Account<'info, AuthorisedOperators>, +} + #[derive(Accounts)] pub struct OpenMarket<'info> { #[account(mut)] diff --git a/programs/monaco_protocol/src/error.rs b/programs/monaco_protocol/src/error.rs index bab393ac..99909a28 100644 --- a/programs/monaco_protocol/src/error.rs +++ b/programs/monaco_protocol/src/error.rs @@ -340,6 +340,9 @@ pub enum CoreError { #[msg("Market: cannot recreate market, provided mint does not match existing market")] MarketMintMismatch, + #[msg("Market: attempted to decrease market unsettled count with non-zero escrow")] + MarketUnsettledCountDecreaseWithNonZeroEscrow, + /* Close Account */ diff --git a/programs/monaco_protocol/src/instructions/market/update_market_status.rs b/programs/monaco_protocol/src/instructions/market/update_market_status.rs index dea1ffc7..e464c33a 100644 --- a/programs/monaco_protocol/src/instructions/market/update_market_status.rs +++ b/programs/monaco_protocol/src/instructions/market/update_market_status.rs @@ -130,6 +130,63 @@ pub fn void( Ok(()) } +pub fn force_void( + market: &mut Market, + void_time: UnixTimestamp, + order_request_queue: &Option, +) -> Result<()> { + require!( + Initializing.eq(&market.market_status) || Open.eq(&market.market_status), + CoreError::VoidMarketNotInitializingOrOpen + ); + + if market.market_status != Initializing { + require!( + order_request_queue.is_some(), + CoreError::VoidMarketRequestQueueNotProvided + ); + require!( + order_request_queue + .as_ref() + .unwrap() + .order_requests + .is_empty(), + CoreError::OrderRequestQueueIsNotEmpty + ); + } + + market.market_settle_timestamp = Option::from(void_time); + market.market_status = ReadyToVoid; + Ok(()) +} + +pub fn force_unsettled_count( + market: &mut Market, + escrow_amount: u64, + new_count: u32, +) -> Result<()> { + require!( + ReadyToVoid.eq(&market.market_status), + CoreError::MarketInvalidStatus + ); + + let old_count = market.unsettled_accounts_count; + if old_count == new_count { + return Ok(()); + } + + if new_count < old_count { + require!( + escrow_amount == 0, + CoreError::MarketUnsettledCountDecreaseWithNonZeroEscrow + ); + } + + market.unsettled_accounts_count = new_count; + + Ok(()) +} + pub fn complete_void(market: &mut Market) -> Result<()> { require!( ReadyToVoid.eq(&market.market_status), @@ -1034,3 +1091,100 @@ mod void_market_tests { assert_eq!(expected_error, result) } } + +#[cfg(test)] +mod force_void_tests { + use crate::error::CoreError; + use crate::instructions::market::force_void; + use crate::state::market_account::{mock_market, MarketStatus}; + use crate::state::market_order_request_queue::{mock_order_request_queue, OrderRequest}; + use anchor_lang::error; + use solana_program::pubkey::Pubkey; + + #[test] + fn force_void_happy_path() { + let market_pk = Pubkey::new_unique(); + let mut market = mock_market(MarketStatus::Open); + let order_request_queue = mock_order_request_queue(market_pk); + + let result = force_void(&mut market, 1665483869, &Some(order_request_queue)); + + assert!(result.is_ok()); + assert_eq!(MarketStatus::ReadyToVoid, market.market_status); + } + + #[test] + fn force_void_incorrect_market_status() { + let market_pk = Pubkey::new_unique(); + let mut market = mock_market(MarketStatus::Settled); + let order_request_queue = mock_order_request_queue(market_pk); + + let result = force_void(&mut market, 1665483869, &Some(order_request_queue)); + + assert!(result.is_err()); + let expected_error = Err(error!(CoreError::VoidMarketNotInitializingOrOpen)); + assert_eq!(expected_error, result) + } + + #[test] + fn force_void_request_queue_not_empty() { + let market_pk = Pubkey::new_unique(); + let mut market = mock_market(MarketStatus::Open); + + let mut order_request_queue = mock_order_request_queue(market_pk); + order_request_queue + .order_requests + .enqueue(OrderRequest::new_unique()); + + let result = force_void(&mut market, 1665483869, &Some(order_request_queue)); + + assert!(result.is_err()); + let expected_error = Err(error!(CoreError::OrderRequestQueueIsNotEmpty)); + assert_eq!(expected_error, result) + } +} + +#[cfg(test)] +mod force_unsettled_count_tests { + use crate::error::CoreError; + use crate::instructions::market::force_unsettled_count; + use crate::state::market_account::{mock_market, MarketStatus}; + use anchor_lang::error; + + #[test] + fn force_unsettled_count_happy_path() { + let mut market = mock_market(MarketStatus::ReadyToVoid); + market.unsettled_accounts_count = 0; + + let result = force_unsettled_count(&mut market, 100, 1); + + assert!(result.is_ok()); + assert_eq!(1, market.unsettled_accounts_count) + } + + #[test] + fn force_unsettled_count_incorrect_market_status() { + let mut market = mock_market(MarketStatus::Open); + market.unsettled_accounts_count = 0; + + let result = force_unsettled_count(&mut market, 100, 1); + + assert!(result.is_err()); + let expected_error = Err(error!(CoreError::MarketInvalidStatus)); + assert_eq!(expected_error, result) + } + + #[test] + fn force_unsettled_count_decrease_with_non_zero_escrow() { + let mut market = mock_market(MarketStatus::ReadyToVoid); + market.unsettled_accounts_count = 1; + + let result = force_unsettled_count(&mut market, 100, 0); + + assert!(result.is_err()); + let expected_error = Err(error!( + CoreError::MarketUnsettledCountDecreaseWithNonZeroEscrow + )); + assert_eq!(expected_error, result) + } +} diff --git a/programs/monaco_protocol/src/lib.rs b/programs/monaco_protocol/src/lib.rs index c19a3399..6b5f3a1c 100644 --- a/programs/monaco_protocol/src/lib.rs +++ b/programs/monaco_protocol/src/lib.rs @@ -8,6 +8,7 @@ use crate::instructions::transfer; use crate::instructions::verify_operator_authority; use crate::state::market_account::{Market, MarketOrderBehaviour}; use crate::state::market_liquidities::LiquiditySource; +use crate::state::market_matching_queue_account::MarketMatchingQueue; use crate::state::market_order_request_queue::{MarketOrderRequestQueue, OrderRequestData}; use crate::state::market_position_account::MarketPosition; use crate::state::operator_account::AuthorisedOperators; @@ -32,7 +33,6 @@ pub mod monaco_protocol { use super::*; use crate::instructions::current_timestamp; use crate::state::market_liquidities::LiquiditySource; - use crate::state::market_matching_queue_account::MarketMatchingQueue; pub const PRICE_SCALE: u8 = 3_u8; pub const SEED_SEPARATOR_CHAR: char = '␞'; @@ -680,6 +680,56 @@ pub mod monaco_protocol { ) } + pub fn force_void_market(ctx: Context) -> Result<()> { + verify_operator_authority( + ctx.accounts.market_operator.key, + &ctx.accounts.authorised_operators, + )?; + verify_market_authority( + ctx.accounts.market_operator.key, + &ctx.accounts.market.authority, + )?; + + let void_time = current_timestamp(); + instructions::market::force_void( + &mut ctx.accounts.market, + void_time, + &ctx.accounts + .order_request_queue + .clone() + .map(|queue: Account| queue.into_inner()), + )?; + + // clear matching queue + if let Some(ref mut _queue) = ctx.accounts.market_matching_queue { + ctx.accounts + .market_matching_queue + .as_mut() + .unwrap() + .matches + .clear(); + } + + Ok(()) + } + + pub fn force_unsettled_count(ctx: Context, new_count: u32) -> Result<()> { + verify_operator_authority( + ctx.accounts.market_operator.key, + &ctx.accounts.authorised_operators, + )?; + verify_market_authority( + ctx.accounts.market_operator.key, + &ctx.accounts.market.authority, + )?; + + instructions::market::force_unsettled_count( + &mut ctx.accounts.market, + ctx.accounts.market_escrow.amount, + new_count, + ) + } + pub fn complete_market_void(ctx: Context) -> Result<()> { instructions::market::complete_void(&mut ctx.accounts.market) } diff --git a/programs/monaco_protocol/src/state/market_matching_queue_account.rs b/programs/monaco_protocol/src/state/market_matching_queue_account.rs index 71d28596..b2168d7c 100644 --- a/programs/monaco_protocol/src/state/market_matching_queue_account.rs +++ b/programs/monaco_protocol/src/state/market_matching_queue_account.rs @@ -108,6 +108,12 @@ impl MatchingQueue { } clone } + + pub fn clear(&mut self) { + self.front = 0; + self.len = 0; + self.empty = true; + } } #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Default)] @@ -322,4 +328,17 @@ mod tests_matching_queue { queue.peek_mut().unwrap().stake = 10; assert_eq!(10, queue.peek().unwrap().stake); } + + #[test] + fn test_clear() { + let mut queue = MatchingQueue::new(3); + queue.enqueue(OrderMatch::default()); + queue.enqueue(OrderMatch::default()); + assert_eq!(2, queue.len()); + + queue.clear(); + assert_eq!(0, queue.len()); + assert_eq!(0, queue.front); + assert_eq!(0, queue.back()); + } } diff --git a/tests/market/force_void_market.ts b/tests/market/force_void_market.ts new file mode 100644 index 00000000..d721679c --- /dev/null +++ b/tests/market/force_void_market.ts @@ -0,0 +1,253 @@ +import { createWalletWithBalance } from "../util/test_util"; +import { monaco } from "../util/wrappers"; +import assert from "assert"; +import { Trades } from "../../npm-client/src"; + +describe("Force void market", () => { + it("void while items remain in matching queue", async () => { + const price = 2.0; + const [p1, p2, market] = await Promise.all([ + createWalletWithBalance(monaco.provider), + createWalletWithBalance(monaco.provider), + monaco.create3WayMarket([price]), + ]); + + // set up purchasers + await market.airdrop(p1, 100.0); + await market.airdrop(p2, 200.0); + const p1Balance = await market.getTokenBalance(p1.publicKey); + const p2Balance = await market.getTokenBalance(p2.publicKey); + + // create orders + const p1OrderPk = await market.forOrder(0, 10.0, price, p1); + const p2OrderPk = await market.againstOrder(0, 20.0, price, p2); + + // ensure there are items still on matching queue + const matchingQueueLen = await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueueLen, 2); + + // force void market + await market.voidMarket(true); + + const matchingQueueLenPostVoid = + await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueueLenPostVoid, 0); + + // void market positions to return funds to purchasers + await market.voidMarketPositionForPurchaser(p1.publicKey); + await market.voidMarketPositionForPurchaser(p2.publicKey); + + // check balances + const p1BalanceAfter = await market.getTokenBalance(p1.publicKey); + const p2BalanceAfter = await market.getTokenBalance(p2.publicKey); + assert.equal(p1Balance, p1BalanceAfter); + assert.equal(p2Balance, p2BalanceAfter); + + // ensure market voiding can be completed + await market.voidOrder(p1OrderPk); + await market.voidOrder(p2OrderPk); + await market.completeVoid(); + const voidedMarket = await monaco.program.account.market.fetch(market.pk); + assert.ok(voidedMarket.marketStatus.voided); + + // set market ready to close + await market.readyToClose(); + const closingMarket = await monaco.program.account.market.fetch(market.pk); + assert.ok(closingMarket.marketStatus.readyToClose); + + // ensure market can be closed + await market.closeOrder(p1OrderPk); + await market.closeOrder(p2OrderPk); + await market.closeMarketPosition(p1.publicKey); + await market.closeMarketPosition(p2.publicKey); + await market.closeMarketMatchingPool(0, price, true); + await market.closeMarketMatchingPool(0, price, false); + await market.closeOutcome(0); + await market.closeOutcome(1); + await market.closeOutcome(2); + await market.closeMarketQueues(); + await market.close(); + + try { + await monaco.program.account.market.fetch(market.pk); + assert.fail("expected Account does not exist or has no data..."); + } catch (e) { + assert.equal( + e.message, + `Account does not exist or has no data ${market.pk.toBase58()}`, + ); + } + }); + + it("void with force_unsettled_account_count", async () => { + /* + If an order is cancelled at market lock, the unsettled_accounts_count is decremented. + If the lock time is then moved forward into the future, the order becomes + available for matching again. This causes issues at settlement and during voiding + as the unsettled_accounts_count will always be incorrect. + + force_unsettled_account_count should allow market operators to correct the + count in these situations + */ + + const price = 2.0; + const [p1, p2, p3, p4, p5, market] = await Promise.all([ + createWalletWithBalance(monaco.provider), + createWalletWithBalance(monaco.provider), + createWalletWithBalance(monaco.provider), + createWalletWithBalance(monaco.provider), + createWalletWithBalance(monaco.provider), + monaco.create3WayMarket( + [price], + false, + 0, + undefined, + undefined, + undefined, + { cancelUnmatched: {} }, + ), + ]); + + // set up purchasers + await market.airdrop(p1, 100.0); + await market.airdrop(p2, 200.0); + await market.airdrop(p3, 300.0); + await market.airdrop(p4, 400.0); + await market.airdrop(p5, 400.0); + const p1Balance = await market.getTokenBalance(p1.publicKey); + const p2Balance = await market.getTokenBalance(p2.publicKey); + const p3Balance = await market.getTokenBalance(p3.publicKey); + const p4Balance = await market.getTokenBalance(p4.publicKey); + const p5Balance = await market.getTokenBalance(p4.publicKey); + + // create orders + const p1OrderPk = await market.forOrder(0, 10.0, price, p1); + const p2OrderPk = await market.againstOrder(0, 20.0, price, p2); + const p3OrderPk = await market.forOrder(0, 20.0, price, p3); + const p4OrderPk = await market.forOrder(0, 10.0, price, p4); + + await market.processMatchingQueue(); + let matchingQueue = await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueue, 0); + + // orders cancelled at lock, causing unsettled_accounts_count to be decremented + await market.updateMarketLockTimeToNow(); + await market.cancelOrderPostMarketLock(p3OrderPk); + await market.cancelOrderPostMarketLock(p4OrderPk); + + const p3Order = await monaco.getOrder(p3OrderPk); + const p4Order = await monaco.getOrder(p4OrderPk); + assert.deepEqual({ matched: {} }, p3Order.status); + assert.deepEqual({ cancelled: {} }, p4Order.status); + + await market.updateMarketLockTime(Date.now() / 1000 + 10); + + matchingQueue = await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueue, 0); + + const p5OrderPk = await market.againstOrder(0, 20.0, price, p5); + matchingQueue = await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueue, 2); + + // process matching queue + try { + await market.processMatchingQueue(); + fail("expected exception"); + } catch (e) { + // + } + matchingQueue = await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueue, 2); + + // force void market + await market.voidMarket(true); + + const matchingQueueLenPostVoid = + await market.getMarketMatchingQueueLength(); + assert.equal(matchingQueueLenPostVoid, 0); + + // void market positions to return funds to purchasers + await market.voidMarketPositionForPurchaser(p1.publicKey); + await market.voidMarketPositionForPurchaser(p2.publicKey); + await market.voidMarketPositionForPurchaser(p3.publicKey); + await market.voidMarketPositionForPurchaser(p4.publicKey); + await market.voidMarketPositionForPurchaser(p5.publicKey); + + // check balances + const p1BalanceAfter = await market.getTokenBalance(p1.publicKey); + const p2BalanceAfter = await market.getTokenBalance(p2.publicKey); + const p3BalanceAfter = await market.getTokenBalance(p3.publicKey); + const p4BalanceAfter = await market.getTokenBalance(p4.publicKey); + const p5BalanceAfter = await market.getTokenBalance(p5.publicKey); + assert.equal(p1Balance, p1BalanceAfter); + assert.equal(p2Balance, p2BalanceAfter); + assert.equal(p3Balance, p3BalanceAfter); + assert.equal(p4Balance, p4BalanceAfter); + assert.equal(p5Balance, p5BalanceAfter); + + // ensure market voiding can be completed + await market.voidOrder(p1OrderPk); + await market.voidOrder(p2OrderPk); + await market.voidOrder(p3OrderPk); + await market.voidOrder(p4OrderPk); + + // voiding p5Order will fail as unsettled_accounts_count is 0 + try { + await market.voidOrder(p5OrderPk); + assert.fail("Expected 'Generic: math operation has failed.'"); + } catch (e) { + assert.ok(e.message.includes("Generic: math operation has failed.")); + } + + // force update unsettled_accounts_count and then void order p5 + await market.forceUnsettledCount(1); + await market.voidOrder(p5OrderPk); + + await market.completeVoid(); + const voidedMarket = await monaco.program.account.market.fetch(market.pk); + assert.ok(voidedMarket.marketStatus.voided); + + // set market ready to close + await market.readyToClose(); + const closingMarket = await monaco.program.account.market.fetch(market.pk); + assert.ok(closingMarket.marketStatus.readyToClose); + + const tradePks = ( + await Trades.tradeQuery(monaco.getRawProgram()) + .filterByMarket(market.pk) + .fetchPublicKeys() + ).data.publicKeys; + + // ensure market can be closed + await market.closeOrder(p1OrderPk); + await market.closeOrder(p2OrderPk); + await market.closeOrder(p3OrderPk); + await market.closeOrder(p4OrderPk); + await market.closeOrder(p5OrderPk); + for (const tradePk of tradePks) { + await market.closeTrade(tradePk); + } + await market.closeMarketPosition(p1.publicKey); + await market.closeMarketPosition(p2.publicKey); + await market.closeMarketPosition(p3.publicKey); + await market.closeMarketPosition(p4.publicKey); + await market.closeMarketPosition(p5.publicKey); + await market.closeMarketMatchingPool(0, price, true); + await market.closeMarketMatchingPool(0, price, false); + await market.closeOutcome(0); + await market.closeOutcome(1); + await market.closeOutcome(2); + await market.closeMarketQueues(); + await market.close(); + + try { + await monaco.program.account.market.fetch(market.pk); + assert.fail("expected 'Account does not exist or has no data' exception"); + } catch (e) { + assert.equal( + e.message, + `Account does not exist or has no data ${market.pk.toBase58()}`, + ); + } + }); +}); diff --git a/tests/util/wrappers.ts b/tests/util/wrappers.ts index 0b9fcd6d..8ad95768 100644 --- a/tests/util/wrappers.ts +++ b/tests/util/wrappers.ts @@ -1393,7 +1393,7 @@ export class MonacoMarket { }); } - async voidMarket() { + async voidMarket(force = false) { const authorisedOperatorsPk = await this.monaco.findMarketAuthorisedOperatorsPda(); @@ -1414,6 +1414,27 @@ export class MonacoMarket { ) ).data.pda; + if (force) { + await this.monaco.program.methods + .forceVoidMarket() + .accounts({ + market: this.pk, + marketOperator: this.marketAuthority + ? this.marketAuthority.publicKey + : this.monaco.operatorPk, + authorisedOperators: authorisedOperatorsPk, + marketMatchingQueue: marketMatchingQueuePk, + orderRequestQueue: orderRequestQueuePk, + }) + .signers(this.marketAuthority ? [this.marketAuthority] : []) + .rpc() + .catch((e) => { + console.error(e); + throw e; + }); + return; + } + await this.monaco.program.methods .voidMarket() .accounts({ @@ -1447,6 +1468,25 @@ export class MonacoMarket { }); } + async forceUnsettledCount(newCount: number) { + await this.monaco.program.methods + .forceUnsettledCount(newCount) + .accounts({ + market: this.pk, + marketEscrow: this.escrowPk, + marketOperator: this.marketAuthority + ? this.marketAuthority.publicKey + : this.monaco.operatorPk, + authorisedOperators: + await this.monaco.findMarketAuthorisedOperatorsPda(), + }) + .rpc() + .catch((e) => { + console.error(e); + throw e; + }); + } + async cacheProductCommissionEscrowPk(productPk: PublicKey) { if (this.productEscrowCache.get(productPk) == undefined) { const wallet = this.monaco.provider.wallet as NodeWallet; @@ -1566,6 +1606,25 @@ export class MonacoMarket { }); } + async updateMarketLockTime(marketLockTime: number) { + await this.monaco.program.methods + .updateMarketLocktime(new BN(marketLockTime)) + .accounts({ + market: this.pk, + authorisedOperators: + await this.monaco.findMarketAuthorisedOperatorsPda(), + marketOperator: this.marketAuthority + ? this.marketAuthority.publicKey + : this.monaco.operatorPk, + }) + .signers(this.marketAuthority ? [this.marketAuthority] : []) + .rpc() + .catch((e) => { + console.error(e); + throw e; + }); + } + async updateMarketEventStartTimeToNow() { await this.monaco.program.methods .updateMarketEventStartTimeToNow() @@ -1674,7 +1733,9 @@ export class MonacoMarket { .closeMarketOutcome() .accounts({ market: this.pk, - authority: this.marketAuthority.publicKey, + authority: this.marketAuthority + ? this.marketAuthority.publicKey + : this.monaco.operatorPk, marketOutcome: ( await findMarketOutcomePda( monaco.program as Program, @@ -1696,7 +1757,79 @@ export class MonacoMarket { matchingQueue: this.matchingQueuePk, commissionPaymentQueue: this.paymentsQueuePk, orderRequestQueue: this.orderRequestQueuePk, - authority: this.marketAuthority.publicKey, + authority: this.marketAuthority + ? this.marketAuthority.publicKey + : this.monaco.operatorPk, + }) + .rpc() + .catch((e) => console.log(e)); + } + + async closeMarketMatchingPool( + outcomeIndex: number, + price: number, + forOutcome: boolean, + ) { + const matchingPool = this.matchingPools[outcomeIndex][price]; + await monaco.program.methods + .closeMarketMatchingPool() + .accounts({ + market: this.pk, + payer: monaco.operatorPk, + marketMatchingPool: matchingPool[forOutcome ? "forOutcome" : "against"], + }) + .rpc() + .catch((e) => console.error(e)); + } + + async closeOrder(orderPk: PublicKey) { + await monaco.program.methods + .closeOrder() + .accounts({ + order: orderPk, + payer: this.monaco.operatorPk, + market: this.pk, + }) + .rpc() + .catch((e) => console.log(e)); + } + + async closeTrade(tradePk: PublicKey) { + await monaco.program.methods + .closeTrade() + .accounts({ + trade: tradePk, + payer: this.monaco.operatorPk, + market: this.pk, + }) + .rpc() + .catch((e) => console.log(e)); + } + + async closeMarketPosition(purchaser: PublicKey) { + const marketPositionPk = await this.cacheMarketPositionPk(purchaser); + await monaco.program.methods + .closeMarketPosition() + .accounts({ + market: this.pk, + marketPosition: marketPositionPk, + payer: purchaser, + }) + .rpc() + .catch((e) => console.log(e)); + } + + async close() { + await monaco.program.methods + .closeMarket() + .accounts({ + market: this.pk, + marketEscrow: this.escrowPk, + marketFunding: this.fundingPk, + authority: this.marketAuthority + ? this.marketAuthority.publicKey + : this.monaco.operatorPk, + tokenProgram: TOKEN_PROGRAM_ID, }) .rpc() .catch((e) => console.log(e));