diff --git a/solana/programs/matching-engine/src/composite/mod.rs b/solana/programs/matching-engine/src/composite/mod.rs index 9a62b0f04..1fd6ee792 100644 --- a/solana/programs/matching-engine/src/composite/mod.rs +++ b/solana/programs/matching-engine/src/composite/mod.rs @@ -97,7 +97,7 @@ pub struct CheckedCustodian<'info> { seeds = [Custodian::SEED_PREFIX], bump = Custodian::BUMP, )] - pub custodian: Account<'info, Custodian>, + pub custodian: Box>, } impl<'info> Deref for CheckedCustodian<'info> { @@ -138,7 +138,7 @@ pub struct OwnerOnlyMut<'info> { seeds = [Custodian::SEED_PREFIX], bump = Custodian::BUMP, )] - pub custodian: Account<'info, Custodian>, + pub custodian: Box>, } #[derive(Accounts)] @@ -171,7 +171,7 @@ pub struct AdminMut<'info> { seeds = [Custodian::SEED_PREFIX], bump = Custodian::BUMP, )] - pub custodian: Account<'info, Custodian>, + pub custodian: Box>, } #[derive(Accounts)] @@ -195,7 +195,7 @@ pub struct LocalTokenRouter<'info> { associated_token::mint = common::USDC_MINT, associated_token::authority = token_router_emitter, )] - pub token_router_mint_recipient: Account<'info, token::TokenAccount>, + pub token_router_mint_recipient: Box>, } #[derive(Accounts)] @@ -208,7 +208,7 @@ pub struct ExistingMutRouterEndpoint<'info> { ], bump = endpoint.bump, )] - pub endpoint: Account<'info, RouterEndpoint>, + pub endpoint: Box>, } impl<'info> Deref for ExistingMutRouterEndpoint<'info> { @@ -644,7 +644,7 @@ pub struct ReserveFastFillSequence<'info> { // This check makes sure that the auction account did not exist before this // instruction was called. require!( - auction.vaa_hash == [0; 32], + auction.vaa_hash == <[u8; 32]>::default(), MatchingEngineError::AuctionExists, ); diff --git a/solana/programs/matching-engine/src/events/auction_settled.rs b/solana/programs/matching-engine/src/events/auction_settled.rs index bda0fd338..aa31d359d 100644 --- a/solana/programs/matching-engine/src/events/auction_settled.rs +++ b/solana/programs/matching-engine/src/events/auction_settled.rs @@ -11,7 +11,7 @@ pub struct SettledTokenAccountInfo { #[derive(Debug)] pub struct AuctionSettled { /// The pubkey of the auction that was settled. - pub auction: Pubkey, + pub fast_vaa_hash: [u8; 32], /// If there was an active auction, this field will have the pubkey of the best offer token that /// was paid back and its balance after repayment. diff --git a/solana/programs/matching-engine/src/events/auction_updated.rs b/solana/programs/matching-engine/src/events/auction_updated.rs index 6ac2c6a93..3bd13f81a 100644 --- a/solana/programs/matching-engine/src/events/auction_updated.rs +++ b/solana/programs/matching-engine/src/events/auction_updated.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; #[derive(Debug)] pub struct AuctionUpdated { pub config_id: u32, - pub auction: Pubkey, + pub fast_vaa_hash: [u8; 32], pub vaa: Option, pub source_chain: u16, pub target_protocol: MessageProtocol, diff --git a/solana/programs/matching-engine/src/events/fast_fill_redeemed.rs b/solana/programs/matching-engine/src/events/fast_fill_redeemed.rs index 02c7057fc..123b5b2dc 100644 --- a/solana/programs/matching-engine/src/events/fast_fill_redeemed.rs +++ b/solana/programs/matching-engine/src/events/fast_fill_redeemed.rs @@ -1,7 +1,9 @@ use anchor_lang::prelude::*; +use crate::state::FastFillSeeds; + #[event] pub struct FastFillRedeemed { pub prepared_by: Pubkey, - pub fast_fill: Pubkey, + pub fast_fill: FastFillSeeds, } diff --git a/solana/programs/matching-engine/src/events/fast_fill_sequence_reserved.rs b/solana/programs/matching-engine/src/events/fast_fill_sequence_reserved.rs index 41326b9f4..bb07a6f28 100644 --- a/solana/programs/matching-engine/src/events/fast_fill_sequence_reserved.rs +++ b/solana/programs/matching-engine/src/events/fast_fill_sequence_reserved.rs @@ -4,5 +4,5 @@ use anchor_lang::prelude::*; #[event] pub struct FastFillSequenceReserved { pub fast_vaa_hash: [u8; 32], - pub fast_fill_seeds: FastFillSeeds, + pub fast_fill: FastFillSeeds, } diff --git a/solana/programs/matching-engine/src/events/order_executed.rs b/solana/programs/matching-engine/src/events/order_executed.rs index a5a7c07cd..55edf8a6e 100644 --- a/solana/programs/matching-engine/src/events/order_executed.rs +++ b/solana/programs/matching-engine/src/events/order_executed.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; #[event] #[derive(Debug)] pub struct OrderExecuted { - pub auction: Pubkey, + pub fast_vaa_hash: [u8; 32], pub vaa: Pubkey, pub source_chain: u16, pub target_protocol: MessageProtocol, diff --git a/solana/programs/matching-engine/src/processor/admin/init_event.rs b/solana/programs/matching-engine/src/processor/admin/init_event.rs new file mode 100644 index 000000000..f46a46b9c --- /dev/null +++ b/solana/programs/matching-engine/src/processor/admin/init_event.rs @@ -0,0 +1,26 @@ +use anchor_lang::prelude::*; + +use crate::composite::*; + +#[derive(Accounts)] +pub struct InitEventSubscription<'info> { + #[account(mut)] + payer: Signer<'info>, + + admin: OwnerOnly<'info>, + + /// This account will be serialized with an event, so its discriminator will change with every + /// update. + /// + /// CHECK: Mutable, must have seeds \["event"\]. + #[account( + init, + payer = payer, + space = 10_240, + seeds = [b"event"], + bump, + )] + event_subscription: UncheckedAccount<'info>, + + system_program: Program<'info, System>, +} diff --git a/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs b/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs index 1d2ed9398..cb3e7a245 100644 --- a/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs +++ b/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs @@ -6,6 +6,7 @@ use crate::{ use anchor_lang::prelude::*; #[derive(Accounts)] +#[event_cpi] pub struct ProposeAuctionParameters<'info> { #[account(mut)] payer: Signer<'info>, @@ -56,7 +57,7 @@ pub fn propose_auction_parameters( )?; // Emit event reflecting the proposal. - emit!(crate::events::Proposed { action }); + emit_cpi!(crate::events::Proposed { action }); // Done. Ok(()) diff --git a/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs b/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs index af7618eb4..4bfc90143 100644 --- a/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs +++ b/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs @@ -6,6 +6,7 @@ use crate::{ use anchor_lang::prelude::*; #[derive(Accounts)] +#[event_cpi] pub struct UpdateAuctionParameters<'info> { #[account(mut)] payer: Signer<'info>, @@ -70,7 +71,7 @@ pub fn update_auction_parameters(ctx: Context) -> Resul let action = ctx.accounts.proposal.action; // Emit event to reflect enacting the proposal. - emit!(crate::events::Enacted { action }); + emit_cpi!(crate::events::Enacted { action }); match action { ProposalAction::UpdateAuctionParameters { id, parameters } => { diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs index 5494dbe43..b15b0a673 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs @@ -9,6 +9,7 @@ use common::{wormhole_cctp_solana, wormhole_io::TypePrefixedPayload}; /// Accounts required for [execute_fast_order_cctp]. #[derive(Accounts)] +#[event_cpi] pub struct ExecuteFastOrderCctp<'info> { #[account(mut)] payer: Signer<'info>, @@ -79,6 +80,7 @@ pub fn handle_execute_fast_order_cctp( let super::PreparedOrderExecution { user_amount: amount, fill, + order_executed_event, } = super::handle_execute_fast_order( &mut ctx.accounts.execute_order, &ctx.accounts.custodian, @@ -182,6 +184,10 @@ pub fn handle_execute_fast_order_cctp( }, )?; + // Emit the order executed event, which liquidators can listen to if this execution ended up + // being penalized so they can collect the base fee at settlement. + emit_cpi!(order_executed_event); + // Finally close the account since it is no longer needed. token::close_account(CpiContext::new_with_signer( token_program.to_account_info(), diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs index d7f0fd774..362bb6e91 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs @@ -98,12 +98,17 @@ pub fn execute_fast_order_local(ctx: Context) -> Result<( let super::PreparedOrderExecution { user_amount: amount, fill, + order_executed_event, } = super::handle_execute_fast_order( &mut ctx.accounts.execute_order, &ctx.accounts.custodian, &ctx.accounts.token_program, )?; + // Emit the order executed event, which liquidators can listen to if this execution ended up + // being penalized so they can collect the base fee at settlement. + emit_cpi!(order_executed_event); + let fast_fill = FastFill::new( fill, ctx.accounts.reserved_sequence.fast_fill_seeds.sequence, @@ -111,6 +116,8 @@ pub fn execute_fast_order_local(ctx: Context) -> Result<( ctx.accounts.payer.key(), amount, ); + + // Emit the fast fill. emit_cpi!(crate::events::LocalFastOrderFilled { seeds: fast_fill.seeds, info: fast_fill.info, diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs index b7c12bf97..935596c07 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs @@ -7,6 +7,7 @@ pub use local::*; use crate::{ composite::*, error::MatchingEngineError, + events::OrderExecuted, state::{Auction, AuctionStatus, MessageProtocol}, utils::{self, auction::DepositPenalty}, }; @@ -20,6 +21,7 @@ use common::messages::{ struct PreparedOrderExecution { pub user_amount: u64, pub fill: Fill, + pub order_executed_event: OrderExecuted, } fn handle_execute_fast_order<'info>( @@ -40,7 +42,7 @@ fn handle_execute_fast_order<'info>( .unwrap() .to_fast_market_order_unchecked(); - let (user_amount, new_status) = { + let (user_amount, new_status, order_executed_event) = { let auction_info = auction.info.as_ref().unwrap(); let current_slot = Clock::get().unwrap().slot; @@ -205,22 +207,19 @@ fn handle_execute_fast_order<'info>( custodian.key().into(), )?; - // Emit the order executed event, which liquidators can listen to if this execution ended up - // being penalized so they can collect the base fee at settlement. - emit!(crate::events::OrderExecuted { - auction: auction.key(), - vaa: fast_vaa.key(), - source_chain: auction_info.source_chain, - target_protocol: auction.target_protocol, - penalized, - }); - ( user_amount, AuctionStatus::Completed { slot: current_slot, execute_penalty: if penalized { penalty.into() } else { None }, }, + OrderExecuted { + fast_vaa_hash: auction.vaa_hash, + vaa: fast_vaa.key(), + source_chain: auction_info.source_chain, + target_protocol: auction.target_protocol, + penalized, + }, ) }; @@ -238,5 +237,6 @@ fn handle_execute_fast_order<'info>( .try_into() .map_err(|_| MatchingEngineError::RedeemerMessageTooLarge)?, }, + order_executed_event, }) } diff --git a/solana/programs/matching-engine/src/processor/auction/offer/improve.rs b/solana/programs/matching-engine/src/processor/auction/offer/improve.rs index 743da66e5..b99dc4d7d 100644 --- a/solana/programs/matching-engine/src/processor/auction/offer/improve.rs +++ b/solana/programs/matching-engine/src/processor/auction/offer/improve.rs @@ -5,6 +5,7 @@ use common::TRANSFER_AUTHORITY_SEED_PREFIX; #[derive(Accounts)] #[instruction(offer_price: u64)] +#[event_cpi] pub struct ImproveOffer<'info> { /// The auction participant needs to set approval to this PDA. /// @@ -134,9 +135,9 @@ pub fn improve_offer(ctx: Context, offer_price: u64) -> Result<()> let info = auction.info.as_ref().unwrap(); // Emit event for auction participants to listen to. - emit!(crate::events::AuctionUpdated { + emit_cpi!(crate::utils::log_emit(crate::events::AuctionUpdated { config_id: info.config_id, - auction: auction.key(), + fast_vaa_hash: auction.vaa_hash, vaa: Default::default(), source_chain: info.source_chain, target_protocol: auction.target_protocol, @@ -148,7 +149,7 @@ pub fn improve_offer(ctx: Context, offer_price: u64) -> Result<()> total_deposit: info.total_deposit(), max_offer_price_allowed: utils::auction::compute_min_allowed_offer(config, info) .checked_sub(1), - }); + })); } // Done. diff --git a/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs b/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs index 190123e94..fab8c53ba 100644 --- a/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs @@ -10,6 +10,7 @@ use common::{messages::raw::LiquidityLayerMessage, TRANSFER_AUTHORITY_SEED_PREFI #[derive(Accounts)] #[instruction(offer_price: u64)] +#[event_cpi] pub struct PlaceInitialOfferCctp<'info> { #[account(mut)] payer: Signer<'info>, @@ -92,7 +93,7 @@ pub struct PlaceInitialOfferCctp<'info> { )] auction: Box>, - offer_token: Account<'info, token::TokenAccount>, + offer_token: Box>, #[account( init, @@ -105,7 +106,7 @@ pub struct PlaceInitialOfferCctp<'info> { ], bump, )] - auction_custody_token: Account<'info, token::TokenAccount>, + auction_custody_token: Box>, usdc: Usdc<'info>, @@ -166,9 +167,9 @@ pub fn place_initial_offer_cctp( let info = ctx.accounts.auction.info.as_ref().unwrap(); // Emit event for auction participants to listen to. - emit!(crate::events::AuctionUpdated { + emit_cpi!(crate::utils::log_emit(crate::events::AuctionUpdated { config_id: info.config_id, - auction: ctx.accounts.auction.key(), + fast_vaa_hash: ctx.accounts.auction.vaa_hash, vaa: ctx.accounts.fast_order_path.fast_vaa.key().into(), source_chain: info.source_chain, target_protocol: ctx.accounts.auction.target_protocol, @@ -180,7 +181,7 @@ pub fn place_initial_offer_cctp( total_deposit: info.total_deposit(), max_offer_price_allowed: utils::auction::compute_min_allowed_offer(config, info) .checked_sub(1), - }); + })); // Finally transfer tokens from the offer authority's token account to the // auction's custody account. diff --git a/solana/programs/matching-engine/src/processor/auction/settle/complete.rs b/solana/programs/matching-engine/src/processor/auction/settle/complete.rs index 00803bc0a..f9249645e 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/complete.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/complete.rs @@ -8,6 +8,7 @@ use anchor_lang::prelude::*; use anchor_spl::token::{self, TokenAccount}; #[derive(Accounts)] +#[event_cpi] pub struct SettleAuctionComplete<'info> { /// CHECK: Must equal prepared_order_response.prepared_by, who paid the rent to post the /// finalized VAA. @@ -247,8 +248,8 @@ fn handle_settle_auction_complete( None => None, }; - emit!(crate::events::AuctionSettled { - auction: ctx.accounts.auction.key(), + emit_cpi!(crate::events::AuctionSettled { + fast_vaa_hash: ctx.accounts.auction.vaa_hash, best_offer_token: settled_best_offer_result, base_fee_token: settled_base_fee_result, with_execute: Default::default(), diff --git a/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs b/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs index 87e0b15e2..b56db9c58 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs @@ -9,6 +9,7 @@ use common::{wormhole_cctp_solana, wormhole_io::TypePrefixedPayload}; /// Accounts required for [settle_auction_none_cctp]. #[derive(Accounts)] +#[event_cpi] pub struct SettleAuctionNoneCctp<'info> { #[account(mut)] payer: Signer<'info>, @@ -102,6 +103,7 @@ fn handle_settle_auction_none_cctp( let super::SettledNone { user_amount: amount, fill, + auction_settled_event, } = super::settle_none_and_prepare_fill(super::SettleNoneAndPrepareFill { prepared_order_response: &mut ctx.accounts.prepared.order_response, prepared_custody_token, @@ -209,6 +211,9 @@ fn handle_settle_auction_none_cctp( }, )?; + // Emit an event indicating that the auction has been settled. + emit_cpi!(auction_settled_event); + // Finally close the account since it is no longer needed. token::close_account(CpiContext::new_with_signer( token_program.to_account_info(), diff --git a/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs b/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs index 6fb6319cd..2cbede99d 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs @@ -126,6 +126,7 @@ pub fn settle_auction_none_local(ctx: Context) -> Result let super::SettledNone { user_amount: amount, fill, + auction_settled_event, } = super::settle_none_and_prepare_fill(super::SettleNoneAndPrepareFill { prepared_order_response: &mut ctx.accounts.prepared.order_response, prepared_custody_token, @@ -135,6 +136,9 @@ pub fn settle_auction_none_local(ctx: Context) -> Result token_program, })?; + // Emit an event indicating that the auction has been settled. + emit_cpi!(auction_settled_event); + let fast_fill = FastFill::new( fill, ctx.accounts.reserved_sequence.fast_fill_seeds.sequence, @@ -142,6 +146,8 @@ pub fn settle_auction_none_local(ctx: Context) -> Result ctx.accounts.payer.key(), amount, ); + + // Emit the fast fill. emit_cpi!(crate::events::LocalFastOrderFilled { seeds: fast_fill.seeds, info: fast_fill.info, diff --git a/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs b/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs index bb26d7a42..9c8233cc7 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs @@ -6,6 +6,7 @@ pub use local::*; use crate::{ composite::*, + events::AuctionSettled, state::{Auction, AuctionStatus, PreparedOrderResponse}, }; use anchor_lang::prelude::*; @@ -24,6 +25,7 @@ struct SettleNoneAndPrepareFill<'ctx, 'info> { struct SettledNone { user_amount: u64, fill: Fill, + auction_settled_event: AuctionSettled, } fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> Result { @@ -81,16 +83,16 @@ fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> R total_penalty: None, }; - emit!(crate::events::AuctionSettled { - auction: auction.key(), + let auction_settled_event = AuctionSettled { + fast_vaa_hash: auction.vaa_hash, best_offer_token: Default::default(), base_fee_token: crate::events::SettledTokenAccountInfo { key: fee_recipient_token.key(), - balance_after: fee_recipient_token.amount.saturating_add(fee) + balance_after: fee_recipient_token.amount.saturating_add(fee), } .into(), with_execute: auction.target_protocol.into(), - }); + }; // TryInto is safe to unwrap here because the redeemer message had to have been able to fit in // the prepared order response account (so it would not have exceed u32::MAX). @@ -105,5 +107,6 @@ fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> R redeemer: prepared_order_response.redeemer, redeemer_message, }, + auction_settled_event, }) } diff --git a/solana/programs/matching-engine/src/processor/fast_fill/complete.rs b/solana/programs/matching-engine/src/processor/fast_fill/complete.rs index 39736eec8..c5afdd088 100644 --- a/solana/programs/matching-engine/src/processor/fast_fill/complete.rs +++ b/solana/programs/matching-engine/src/processor/fast_fill/complete.rs @@ -9,6 +9,7 @@ use common::wormhole_cctp_solana::wormhole::SOLANA_CHAIN; /// Accounts required for [complete_fast_fill]. #[derive(Accounts)] +#[event_cpi] pub struct CompleteFastFill<'info> { /// Custodian, which may be used in the future. custodian: CheckedCustodian<'info>, @@ -30,7 +31,7 @@ pub struct CompleteFastFill<'info> { bump = fast_fill.seeds.bump, constraint = !fast_fill.redeemed @ MatchingEngineError::FastFillAlreadyRedeemed, )] - fast_fill: Account<'info, FastFill>, + fast_fill: Box>, /// Only the registered local Token Router program can call this instruction. It is allowed to /// invoke this instruction by using its emitter (i.e. its Custodian account) as a signer. We @@ -42,7 +43,7 @@ pub struct CompleteFastFill<'info> { mut, token::mint = local_custody_token.mint, )] - token_router_custody_token: Account<'info, token::TokenAccount>, + token_router_custody_token: Box>, #[account( constraint = { @@ -81,9 +82,9 @@ pub fn complete_fast_fill(ctx: Context) -> Result<()> { ctx.accounts.fast_fill.redeemed = true; // Emit event that the fast fill is redeemed. Listeners can close this account. - emit!(crate::events::FastFillRedeemed { + emit_cpi!(crate::events::FastFillRedeemed { prepared_by: ctx.accounts.fast_fill.info.prepared_by, - fast_fill: ctx.accounts.fast_fill.key(), + fast_fill: ctx.accounts.fast_fill.seeds, }); // Finally transfer to local token router's token account. diff --git a/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/active_auction.rs b/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/active_auction.rs index 7f9b7eadc..124c206bf 100644 --- a/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/active_auction.rs +++ b/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/active_auction.rs @@ -2,6 +2,7 @@ use crate::{composite::*, error::MatchingEngineError, state::AuctionConfig}; use anchor_lang::prelude::*; #[derive(Accounts)] +#[event_cpi] pub struct ReserveFastFillSequenceActiveAuction<'info> { reserve_sequence: ReserveFastFillSequence<'info>, @@ -34,10 +35,15 @@ pub fn reserve_fast_fill_sequence_active_auction( let beneficiary = ctx.accounts.reserve_sequence.payer.key(); let fast_vaa_hash = ctx.accounts.reserve_sequence.auction.vaa_hash; - super::set_reserved_sequence_data( + let sequence_reserved_event = super::set_reserved_sequence_data( &mut ctx.accounts.reserve_sequence, &ctx.bumps.reserve_sequence, fast_vaa_hash, beneficiary, - ) + )?; + + // Emit an event indicating that the fast fill sequence has been reserved. + emit_cpi!(sequence_reserved_event); + + Ok(()) } diff --git a/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/mod.rs b/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/mod.rs index f8f9fbd61..7cc3a9061 100644 --- a/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/mod.rs +++ b/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/mod.rs @@ -7,6 +7,7 @@ pub use no_auction::*; use crate::{ composite::*, error::MatchingEngineError, + events::FastFillSequenceReserved, state::{ FastFillSeeds, FastFillSequencer, FastFillSequencerSeeds, ReservedFastFillSequence, ReservedFastFillSequenceSeeds, @@ -20,7 +21,7 @@ fn set_reserved_sequence_data( bumps: &ReserveFastFillSequenceBumps, fast_vaa_hash: [u8; 32], beneficiary: Pubkey, -) -> Result<()> { +) -> Result { let sequencer = &mut reserve_sequence.sequencer; // If the fast fill sequencer was just created, we need to set it with data. @@ -76,13 +77,10 @@ fn set_reserved_sequence_data( .checked_add(1) .ok_or_else(|| MatchingEngineError::U64Overflow)?; - // Emit an event to help auction participants track the fast fill sequence so they can more + // Prepare an event to help auction participants track the fast fill sequence so they can more // easily execute local orders. - emit!(crate::events::FastFillSequenceReserved { + Ok(FastFillSequenceReserved { fast_vaa_hash, - fast_fill_seeds, - }); - - // Done. - Ok(()) + fast_fill: fast_fill_seeds, + }) } diff --git a/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/no_auction.rs b/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/no_auction.rs index 4070bd244..5b17e1eb4 100644 --- a/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/no_auction.rs +++ b/solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/no_auction.rs @@ -2,6 +2,7 @@ use crate::{composite::*, error::MatchingEngineError, state::PreparedOrderRespon use anchor_lang::prelude::*; #[derive(Accounts)] +#[event_cpi] pub struct ReserveFastFillSequenceNoAuction<'info> { #[account( constraint = reserve_sequence.auction.info.is_none() @ MatchingEngineError::AuctionExists, @@ -35,10 +36,15 @@ pub fn reserve_fast_fill_sequence_no_auction( prepared_order_response.new_auction_placeholder(ctx.bumps.reserve_sequence.auction), ); - super::set_reserved_sequence_data( + let sequence_reserved_event = super::set_reserved_sequence_data( &mut ctx.accounts.reserve_sequence, &ctx.bumps.reserve_sequence, prepared_order_response.seeds.fast_vaa_hash, prepared_order_response.prepared_by, - ) + )?; + + // Emit an event indicating that the fast fill sequence has been reserved. + emit_cpi!(sequence_reserved_event); + + Ok(()) } diff --git a/solana/programs/matching-engine/src/utils/mod.rs b/solana/programs/matching-engine/src/utils/mod.rs index 95a8a397e..5fc71c618 100644 --- a/solana/programs/matching-engine/src/utils/mod.rs +++ b/solana/programs/matching-engine/src/utils/mod.rs @@ -57,3 +57,13 @@ pub fn checked_deserialize_token_account( .map(Box::new) } } + +/// This method is just used out of convenience to return the same event that was emitted so +/// something like `emit_cpi!` can be used on the same event. +pub fn log_emit(event: E) -> E +where + E: anchor_lang::Event, +{ + emit!(event); + event +} diff --git a/solana/programs/token-router/src/processor/redeem_fill/fast.rs b/solana/programs/token-router/src/processor/redeem_fill/fast.rs index 562f7c723..cb8de64e5 100644 --- a/solana/programs/token-router/src/processor/redeem_fill/fast.rs +++ b/solana/programs/token-router/src/processor/redeem_fill/fast.rs @@ -73,6 +73,9 @@ pub struct RedeemFastFill<'info> { #[account(mut)] matching_engine_local_custody_token: UncheckedAccount<'info>, + /// CHECK: Seeds must be \["__event_authority"] (Matching Engine program). + matching_engine_event_authority: UncheckedAccount<'info>, + matching_engine_program: Program<'info, matching_engine::program::MatchingEngine>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, @@ -112,6 +115,11 @@ fn handle_redeem_fast_fill(ctx: Context) -> Result<()> { .matching_engine_local_custody_token .to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), + event_authority: ctx + .accounts + .matching_engine_event_authority + .to_account_info(), + program: ctx.accounts.matching_engine_program.to_account_info(), }, &[Custodian::SIGNER_SEEDS], ))?; diff --git a/solana/ts/src/idl/json/matching_engine.json b/solana/ts/src/idl/json/matching_engine.json index b2ed77acf..1900ecb71 100644 --- a/solana/ts/src/idl/json/matching_engine.json +++ b/solana/ts/src/idl/json/matching_engine.json @@ -437,6 +437,12 @@ }, { "name": "token_program" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -802,6 +808,12 @@ ] } ] + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -1009,6 +1021,12 @@ }, { "name": "token_program" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [ @@ -1273,6 +1291,12 @@ }, { "name": "token_program" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [ @@ -1528,6 +1552,12 @@ }, { "name": "epoch_schedule" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [ @@ -1649,6 +1679,12 @@ }, { "name": "auction_config" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -1772,6 +1808,12 @@ "closed. This instruction will not allow this account to be provided if there is an existing", "auction, which would enforce the order be executed when it is time to complete the auction." ] + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -1883,6 +1925,12 @@ }, { "name": "token_program" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -2062,6 +2110,12 @@ ] } ] + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -2287,6 +2341,12 @@ }, { "name": "system_program" + }, + { + "name": "event_authority" + }, + { + "name": "program" } ], "args": [] @@ -3475,11 +3535,16 @@ "kind": "struct", "fields": [ { - "name": "auction", + "name": "fast_vaa_hash", "docs": [ "The pubkey of the auction that was settled." ], - "type": "pubkey" + "type": { + "array": [ + "u8", + 32 + ] + } }, { "name": "best_offer_token", @@ -3580,8 +3645,13 @@ "type": "u32" }, { - "name": "auction", - "type": "pubkey" + "name": "fast_vaa_hash", + "type": { + "array": [ + "u8", + 32 + ] + } }, { "name": "vaa", @@ -3851,7 +3921,11 @@ }, { "name": "fast_fill", - "type": "pubkey" + "type": { + "defined": { + "name": "FastFillSeeds" + } + } } ] } @@ -3913,7 +3987,7 @@ } }, { - "name": "fast_fill_seeds", + "name": "fast_fill", "type": { "defined": { "name": "FastFillSeeds" @@ -4055,8 +4129,13 @@ "kind": "struct", "fields": [ { - "name": "auction", - "type": "pubkey" + "name": "fast_vaa_hash", + "type": { + "array": [ + "u8", + 32 + ] + } }, { "name": "vaa", diff --git a/solana/ts/src/idl/json/token_router.json b/solana/ts/src/idl/json/token_router.json index 777d9e5ce..bb2016ac5 100644 --- a/solana/ts/src/idl/json/token_router.json +++ b/solana/ts/src/idl/json/token_router.json @@ -845,6 +845,9 @@ ], "writable": true }, + { + "name": "matching_engine_event_authority" + }, { "name": "matching_engine_program" }, diff --git a/solana/ts/src/idl/ts/matching_engine.ts b/solana/ts/src/idl/ts/matching_engine.ts index 055f7e149..7ccf19c65 100644 --- a/solana/ts/src/idl/ts/matching_engine.ts +++ b/solana/ts/src/idl/ts/matching_engine.ts @@ -443,6 +443,12 @@ export type MatchingEngine = { }, { "name": "tokenProgram" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -808,6 +814,12 @@ export type MatchingEngine = { ] } ] + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -1015,6 +1027,12 @@ export type MatchingEngine = { }, { "name": "tokenProgram" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [ @@ -1279,6 +1297,12 @@ export type MatchingEngine = { }, { "name": "tokenProgram" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [ @@ -1534,6 +1558,12 @@ export type MatchingEngine = { }, { "name": "epochSchedule" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [ @@ -1655,6 +1685,12 @@ export type MatchingEngine = { }, { "name": "auctionConfig" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -1778,6 +1814,12 @@ export type MatchingEngine = { "closed. This instruction will not allow this account to be provided if there is an existing", "auction, which would enforce the order be executed when it is time to complete the auction." ] + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -1889,6 +1931,12 @@ export type MatchingEngine = { }, { "name": "tokenProgram" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -2068,6 +2116,12 @@ export type MatchingEngine = { ] } ] + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -2293,6 +2347,12 @@ export type MatchingEngine = { }, { "name": "systemProgram" + }, + { + "name": "eventAuthority" + }, + { + "name": "program" } ], "args": [] @@ -3481,11 +3541,16 @@ export type MatchingEngine = { "kind": "struct", "fields": [ { - "name": "auction", + "name": "fastVaaHash", "docs": [ "The pubkey of the auction that was settled." ], - "type": "pubkey" + "type": { + "array": [ + "u8", + 32 + ] + } }, { "name": "bestOfferToken", @@ -3586,8 +3651,13 @@ export type MatchingEngine = { "type": "u32" }, { - "name": "auction", - "type": "pubkey" + "name": "fastVaaHash", + "type": { + "array": [ + "u8", + 32 + ] + } }, { "name": "vaa", @@ -3857,7 +3927,11 @@ export type MatchingEngine = { }, { "name": "fastFill", - "type": "pubkey" + "type": { + "defined": { + "name": "fastFillSeeds" + } + } } ] } @@ -3919,7 +3993,7 @@ export type MatchingEngine = { } }, { - "name": "fastFillSeeds", + "name": "fastFill", "type": { "defined": { "name": "fastFillSeeds" @@ -4061,8 +4135,13 @@ export type MatchingEngine = { "kind": "struct", "fields": [ { - "name": "auction", - "type": "pubkey" + "name": "fastVaaHash", + "type": { + "array": [ + "u8", + 32 + ] + } }, { "name": "vaa", diff --git a/solana/ts/src/idl/ts/token_router.ts b/solana/ts/src/idl/ts/token_router.ts index b95c46435..e83ff3ba6 100644 --- a/solana/ts/src/idl/ts/token_router.ts +++ b/solana/ts/src/idl/ts/token_router.ts @@ -851,6 +851,9 @@ export type TokenRouter = { ], "writable": true }, + { + "name": "matchingEngineEventAuthority" + }, { "name": "matchingEngineProgram" }, diff --git a/solana/ts/src/matchingEngine/index.ts b/solana/ts/src/matchingEngine/index.ts index 5b51455f8..74422e341 100644 --- a/solana/ts/src/matchingEngine/index.ts +++ b/solana/ts/src/matchingEngine/index.ts @@ -31,7 +31,7 @@ import { writeUint64BE, } from "../common"; import { UpgradeManagerProgram } from "../upgradeManager"; -import { BPF_LOADER_UPGRADEABLE_PROGRAM_ID, programDataAddress } from "../utils"; +import { ArrayQueue, BPF_LOADER_UPGRADEABLE_PROGRAM_ID, programDataAddress } from "../utils"; import { VaaAccount } from "../wormhole"; import { Auction, @@ -55,6 +55,7 @@ import { RouterEndpoint, } from "./state"; import { ChainId, toChainId, isChainId } from "@wormhole-foundation/sdk-base"; +import { decodeIdlAccount } from "anchor-0.29.0/dist/cjs/idl"; export const PROGRAM_IDS = [ "MatchingEngine11111111111111111111111111111", @@ -131,6 +132,7 @@ export type RedeemFastFillAccounts = { fromRouterEndpoint: PublicKey; toRouterEndpoint: PublicKey; localCustodyToken: PublicKey; + eventAuthority: PublicKey; matchingEngineProgram: PublicKey; }; @@ -145,7 +147,7 @@ export type SettledTokenAccountInfo = { }; export type AuctionSettled = { - auction: PublicKey; + fastVaaHash: Array; bestOfferToken: SettledTokenAccountInfo | null; baseFeeToken: SettledTokenAccountInfo | null; withExecute: MessageProtocol | null; @@ -153,7 +155,7 @@ export type AuctionSettled = { export type AuctionUpdated = { configId: number; - auction: PublicKey; + fastVaaHash: Array; vaa: PublicKey | null; sourceChain: number; targetProtocol: MessageProtocol; @@ -167,7 +169,7 @@ export type AuctionUpdated = { }; export type OrderExecuted = { - auction: PublicKey; + fastVaaHash: Array; vaa: PublicKey; targetProtocol: MessageProtocol; }; @@ -188,12 +190,23 @@ export type LocalFastOrderFilled = { export type FastFillSequenceReserved = { fastVaaHash: Array; - fastFillSeeds: FastFillSeeds; + fastFill: FastFillSeeds; }; export type FastFillRedeemed = { preparedBy: PublicKey; - fastFill: PublicKey; + fastFill: FastFillSeeds; +}; + +export type MatchingEngineEvent = { + auctionSettled?: AuctionSettled; + auctionUpdated?: AuctionUpdated; + orderExecuted?: OrderExecuted; + proposed?: Proposed; + enacted?: Enacted; + localFastOrderFilled?: LocalFastOrderFilled; + fastFillSequenceReserved?: FastFillSequenceReserved; + fastFillRedeemed?: FastFillRedeemed; }; export type FastOrderPathComposite = { @@ -240,46 +253,34 @@ export class MatchingEngineProgram { return this._mint; } - onAuctionSettled(callback: (event: AuctionSettled, slot: number, signature: string) => void) { - return this.program.addEventListener("auctionSettled", callback); - } - + /// NOTE: This listener can be unreliable because it depends on parsing program logs. For a more + /// reliable method, use `onEventCpi` and filter for the "auctionUpdated" event. onAuctionUpdated(callback: (event: AuctionUpdated, slot: number, signature: string) => void) { return this.program.addEventListener("auctionUpdated", callback); } - onOrderExecuted(callback: (event: OrderExecuted, slot: number, signature: string) => void) { - return this.program.addEventListener("orderExecuted", callback); - } - - onProposed(callback: (event: Proposed, slot: number, signature: string) => void) { - return this.program.addEventListener("proposed", callback); - } - - onEnacted(callback: (event: Enacted, slot: number, signature: string) => void) { - return this.program.addEventListener("enacted", callback); - } - - onFilledLocalFastOrder( + /// NOTE: This function is not optimized to minimize RPC calls. + onEventCpi( callback: ( - event: LocalFastOrderFilled, + event: MatchingEngineEvent, eventSlot: number, signature: string, currentSlotInfo?: SlotInfo, ) => void, commitment: Finality = "confirmed", - ) { + ): number { const program = this.program; const connection = program.provider.connection; - const signatureSlots: { signature: string; eventSlot: number }[] = []; + const unprocessedTxs = new ArrayQueue<{ signature: string; eventSlot: number }>(); + const observedTxs = new Map>(); connection.onSlotChange(async (slotInfo) => { // TODO: Make this more efficient by fetching multiple parsed transactions. - while (signatureSlots.length > 0) { - const { signature, eventSlot } = signatureSlots[0]; + while (!unprocessedTxs.isEmpty()) { + const { signature, eventSlot } = unprocessedTxs.head(); - const parsedTx = await program.provider.connection.getParsedTransaction(signature, { + const parsedTx = await connection.getParsedTransaction(signature, { commitment, maxSupportedTransactionVersion: 0, }); @@ -288,8 +289,7 @@ export class MatchingEngineProgram { break; } - // Finally dequeue. - signatureSlots.shift(); + unprocessedTxs.dequeue(); // Find the event. for (const innerIx of parsedTx.meta?.innerInstructions!) { @@ -307,45 +307,46 @@ export class MatchingEngineProgram { utils.bytes.base64.encode(data.subarray(8)), ); - if (decoded !== null && decoded.name === "localFastOrderFilled") { - return callback(decoded.data, eventSlot, signature, slotInfo); + if (decoded !== null) { + callback( + { + [decoded.name]: decoded.data, + }, + eventSlot, + signature, + slotInfo, + ); } } } } - }); - this.onOrderExecuted(async (orderExecutedEvent, eventSlot, signature) => { - if (orderExecutedEvent.targetProtocol.local === undefined) { - return; + // Clean up consumed. + for (const [slot] of observedTxs) { + if (slot < slotInfo.slot - 64) { + observedTxs.delete(slot); + } } - - signatureSlots.push({ signature, eventSlot }); }); - // No new listener IDs are created on subsequent addEventListener calls. - return this.onAuctionSettled(async (auctionSettledEvent, eventSlot, signature) => { - const withExecute = auctionSettledEvent.withExecute; - if (withExecute === null || withExecute.local === undefined) { + return connection.onLogs(program.programId, (logs, ctx) => { + const signature = logs.signature; + const eventSlot = ctx.slot; + + if (!observedTxs.has(eventSlot)) { + observedTxs.set(eventSlot, new Set()); + } + + const observed = observedTxs.get(eventSlot)!; + if (observed.has(signature)) { return; } - signatureSlots.push({ signature, eventSlot }); + unprocessedTxs.enqueue({ signature, eventSlot }); + observed.add(signature); }); } - onFastFillSequenceReserved( - callback: (event: FastFillSequenceReserved, slot: number, signature: string) => void, - ) { - return this.program.addEventListener("fastFillSequenceReserved", callback); - } - - onFastFillRedeemed( - callback: (event: FastFillRedeemed, slot: number, signature: string) => void, - ) { - return this.program.addEventListener("fastFillRedeemed", callback); - } - eventAuthorityAddress(): PublicKey { return PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], this.ID)[0]; } @@ -1085,6 +1086,8 @@ export class MatchingEngineProgram { proposal, epochSchedule: SYSVAR_EPOCH_SCHEDULE_PUBKEY, systemProgram: SystemProgram.programId, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -1147,6 +1150,8 @@ export class MatchingEngineProgram { proposal, auctionConfig, systemProgram: SystemProgram.programId, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -1374,6 +1379,8 @@ export class MatchingEngineProgram { usdc: this.usdcComposite(), tokenProgram: splToken.TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); @@ -1461,6 +1468,8 @@ export class MatchingEngineProgram { ), offerToken: splToken.getAssociatedTokenAddressSync(this.mint, participant), tokenProgram: splToken.TOKEN_PROGRAM_ID, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); @@ -1640,6 +1649,8 @@ export class MatchingEngineProgram { auction, bestOfferToken, tokenProgram: splToken.TOKEN_PROGRAM_ID, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -1865,6 +1876,8 @@ export class MatchingEngineProgram { tokenProgram: splToken.TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, sysvars: this.requiredSysvarsComposite(), + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -2062,6 +2075,8 @@ export class MatchingEngineProgram { tokenProgram: splToken.TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, sysvars: this.requiredSysvarsComposite(), + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -2188,6 +2203,8 @@ export class MatchingEngineProgram { .accounts({ reserveSequence, auctionConfig, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -2229,6 +2246,8 @@ export class MatchingEngineProgram { .accounts({ reserveSequence, preparedOrderResponse, + eventAuthority: this.eventAuthorityAddress(), + program: this.ID, }) .instruction(); } @@ -2353,6 +2372,7 @@ export class MatchingEngineProgram { fromRouterEndpoint: this.routerEndpointAddress(sourceChain), toRouterEndpoint: this.routerEndpointAddress(toChainId("Solana")), localCustodyToken: this.localCustodyTokenAddress(sourceChain), + eventAuthority: this.eventAuthorityAddress(), matchingEngineProgram: this.ID, }; } diff --git a/solana/ts/src/tokenRouter/index.ts b/solana/ts/src/tokenRouter/index.ts index 666dde6cc..c667768dc 100644 --- a/solana/ts/src/tokenRouter/index.ts +++ b/solana/ts/src/tokenRouter/index.ts @@ -103,6 +103,7 @@ export type RedeemFastFillAccounts = { matchingEngineFromEndpoint: PublicKey; matchingEngineToEndpoint: PublicKey; matchingEngineLocalCustodyToken: PublicKey; + matchingEngineEventAuthority: PublicKey; matchingEngineProgram: PublicKey; }; @@ -729,6 +730,7 @@ export class TokenRouterProgram { fromRouterEndpoint: matchingEngineFromEndpoint, toRouterEndpoint: matchingEngineToEndpoint, localCustodyToken: matchingEngineLocalCustodyToken, + eventAuthority: matchingEngineEventAuthority, matchingEngineProgram, } = await this.matchingEngineProgram().redeemFastFillAccounts(fastFill); @@ -740,6 +742,7 @@ export class TokenRouterProgram { matchingEngineFromEndpoint, matchingEngineToEndpoint, matchingEngineLocalCustodyToken, + matchingEngineEventAuthority, matchingEngineProgram, }; } @@ -754,6 +757,7 @@ export class TokenRouterProgram { matchingEngineFromEndpoint, matchingEngineToEndpoint, matchingEngineLocalCustodyToken, + matchingEngineEventAuthority, matchingEngineProgram, } = await this.redeemFastFillAccounts(fastFill); @@ -770,6 +774,7 @@ export class TokenRouterProgram { matchingEngineFromEndpoint, matchingEngineToEndpoint, matchingEngineLocalCustodyToken, + matchingEngineEventAuthority, matchingEngineProgram, tokenProgram: splToken.TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, diff --git a/solana/ts/src/utils.ts b/solana/ts/src/utils.ts index cc0fb1b30..3d3f641ad 100644 --- a/solana/ts/src/utils.ts +++ b/solana/ts/src/utils.ts @@ -10,3 +10,64 @@ export function programDataAddress(programId: PublicKey) { BPF_LOADER_UPGRADEABLE_PROGRAM_ID, )[0]; } + +export class ArrayQueue { + private _data: Array; + private _size: number = 0; + private _index: number = 0; + + constructor(capacity?: number) { + this._data = new Array(capacity ?? 256).fill(null); + } + + head(): T { + if (this.isEmpty()) { + throw new Error("queue is empty"); + } + + return this._data[this._index]!; + } + + enqueue(value: T): void { + const data = this._data; + const size = this._size; + const index = this._index; + + if (size + 1 > data.length) { + this.resize(); + } + + data[(index + size) % data.length] = value; + ++this._size; + } + + dequeue(): void { + const data = this._data; + const index = this._index; + + this._index = (index + 1) % data.length; + --this._size; + } + + resize(): void { + const data = this._data; + const size = this._size; + const index = this._index; + + const newData = new Array(size * 2); + for (let i = 0; i < size; ++i) { + newData[i] = data[(index + i) % data.length]; + } + + this._data = newData; + this._index = 0; + } + + isEmpty(): boolean { + return this._size == 0; + } + + length(): number { + return this._size; + } +} diff --git a/solana/ts/tests/04__interaction.ts b/solana/ts/tests/04__interaction.ts index f19c00146..4769fee55 100644 --- a/solana/ts/tests/04__interaction.ts +++ b/solana/ts/tests/04__interaction.ts @@ -1,4 +1,4 @@ -import { BN } from "@coral-xyz/anchor"; +import { BN, EventParser } from "@coral-xyz/anchor"; import * as splToken from "@solana/spl-token"; import { AddressLookupTableProgram, @@ -423,18 +423,28 @@ describe("Matching Engine <> Token Router", function () { describe("Settle Auction", function () { const emittedEvents: EmittedFilledLocalFastOrder[] = []; - let listenerId: number | null; + let listenerId: number | undefined; describe("Settle No Auction (Local)", function () { before("Start Event Listener", async function () { - listenerId = matchingEngine.onFilledLocalFastOrder((event, slot, signature) => { - emittedEvents.push({ event, slot, signature }); + // listenerId = matchingEngine.onFilledLocalFastOrder((event, slot, signature) => { + // emittedEvents.push({ event, slot, signature }); + // }); + listenerId = matchingEngine.onEventCpi((event, slot, signature) => { + const { localFastOrderFilled } = event; + if (localFastOrderFilled !== undefined) { + emittedEvents.push({ + event: localFastOrderFilled, + slot, + signature, + }); + } }); }); after("Stop Event Listener", async function () { - if (listenerId !== null) { - matchingEngine.program.removeEventListener(listenerId!); + if (listenerId !== undefined) { + matchingEngine.program.removeEventListener(listenerId); } }); @@ -457,17 +467,27 @@ describe("Matching Engine <> Token Router", function () { describe("Matching Engine -- Execute Fast Order (Local)", function () { const emittedEvents: EmittedFilledLocalFastOrder[] = []; - let listenerId: number | null; + let listenerId: number | undefined; before("Start Event Listener", async function () { - listenerId = matchingEngine.onFilledLocalFastOrder((event, slot, signature) => { - emittedEvents.push({ event, slot, signature }); + // listenerId = matchingEngine.onFilledLocalFastOrder((event, slot, signature) => { + // emittedEvents.push({ event, slot, signature }); + // }); + listenerId = matchingEngine.onEventCpi((event, slot, signature) => { + const { localFastOrderFilled } = event; + if (localFastOrderFilled !== undefined) { + emittedEvents.push({ + event: localFastOrderFilled, + slot, + signature, + }); + } }); }); after("Stop Event Listener", async function () { - if (listenerId !== null) { - matchingEngine.program.removeEventListener(listenerId!); + if (listenerId !== undefined) { + matchingEngine.program.removeEventListener(listenerId); } }); @@ -497,19 +517,26 @@ describe("Matching Engine <> Token Router", function () { describe("Token Router -- Redeem Fast Fill", function () { const emittedEvents: EmittedFilledLocalFastOrder[] = []; - let listenerId: number | null; + let listenerId: number | undefined; const localVariables = new Map(); before("Start Event Listener", async function () { - listenerId = matchingEngine.onFilledLocalFastOrder((event, slot, signature) => { - emittedEvents.push({ event, slot, signature }); + listenerId = matchingEngine.onEventCpi((event, slot, signature) => { + const { localFastOrderFilled } = event; + if (localFastOrderFilled !== undefined) { + emittedEvents.push({ + event: localFastOrderFilled, + slot, + signature, + }); + } }); }); after("Stop Event Listener", async function () { - if (listenerId !== null) { - matchingEngine.program.removeEventListener(listenerId!); + if (listenerId !== undefined) { + matchingEngine.program.removeEventListener(listenerId); } }); @@ -1367,9 +1394,16 @@ describe("Matching Engine <> Token Router", function () { ).to.eql(fastFill); // Check event. + let retryCount = 0; while (emittedEvents.length == 0) { - console.log("waiting..."); await new Promise((resolve) => setTimeout(resolve, 200)); + ++retryCount; + + console.log(`waiting... ${retryCount}`); + + if (retryCount > 20) { + throw new Error("Timed out waiting for event"); + } } const { event, slot, signature } = emittedEvents.shift()!; @@ -1502,9 +1536,16 @@ describe("Matching Engine <> Token Router", function () { ).to.eql(fastFill); // Check event. + let retryCount = 0; while (emittedEvents.length == 0) { - console.log("waiting..."); await new Promise((resolve) => setTimeout(resolve, 200)); + ++retryCount; + + console.log(`waiting... ${retryCount}`); + + if (retryCount > 20) { + throw new Error("Timed out waiting for event"); + } } const { event, slot, signature } = emittedEvents.shift()!;