diff --git a/.github/workflows/solana.yml b/.github/workflows/solana.yml index 2aa6261d..0bd6fea4 100644 --- a/.github/workflows/solana.yml +++ b/.github/workflows/solana.yml @@ -141,6 +141,12 @@ jobs: - name: Set default Rust toolchain run: rustup default stable working-directory: ./solana + - name: Git Submodule Update + run: | + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + git submodule update --init --recursive + working-directory: ./solana - name: make anchor-test-upgrade run: make anchor-test-upgrade working-directory: ./solana diff --git a/solana/modules/matching-engine-testing/tests/shimful/mod.rs b/solana/modules/matching-engine-testing/tests/shimful/mod.rs index dcbf6c9a..cb7c4830 100644 --- a/solana/modules/matching-engine-testing/tests/shimful/mod.rs +++ b/solana/modules/matching-engine-testing/tests/shimful/mod.rs @@ -4,4 +4,5 @@ pub mod post_message; pub mod shims_execute_order; pub mod shims_make_offer; pub mod shims_prepare_order_response; +pub mod shims_settle_auction_none; pub mod verify_shim; diff --git a/solana/modules/matching-engine-testing/tests/shimful/shims_execute_order.rs b/solana/modules/matching-engine-testing/tests/shimful/shims_execute_order.rs index a62878ed..a0573f02 100644 --- a/solana/modules/matching-engine-testing/tests/shimful/shims_execute_order.rs +++ b/solana/modules/matching-engine-testing/tests/shimful/shims_execute_order.rs @@ -1,14 +1,13 @@ -use crate::testing_engine::config::{ - ExecuteOrderInstructionConfig, ExpectedError, InstructionConfig, -}; +use crate::testing_engine::config::{ExecuteOrderInstructionConfig, InstructionConfig}; use crate::testing_engine::setup::{TestingContext, TransferDirection}; -use crate::testing_engine::state::TestingEngineState; +use crate::testing_engine::state::{OrderExecutedState, TestingEngineState}; use super::super::utils; use anchor_spl::token::spl_token; use common::wormhole_cctp_solana::cctp::{ MESSAGE_TRANSMITTER_PROGRAM_ID, TOKEN_MESSENGER_MINTER_PROGRAM_ID, }; +use matching_engine::accounts::CctpDepositForBurn; use matching_engine::fallback::execute_order::{ExecuteOrderCctpShim, ExecuteOrderShimAccounts}; use solana_program_test::ProgramTestContext; use solana_sdk::{pubkey::Pubkey, signer::Signer, sysvar::SysvarId, transaction::Transaction}; @@ -22,110 +21,23 @@ use wormhole_svm_definitions::{ EVENT_AUTHORITY_SEED, }; -pub struct ExecuteOrderFallbackAccounts { - pub signer: Pubkey, - pub custodian: Pubkey, - pub fast_market_order_address: Pubkey, - pub active_auction: Pubkey, - pub active_auction_custody_token: Pubkey, - pub active_auction_config: Pubkey, - pub active_auction_best_offer_token: Pubkey, - pub initial_offer_token: Pubkey, - pub initial_participant: Pubkey, - pub to_router_endpoint: Pubkey, - pub remote_token_messenger: Pubkey, - pub token_messenger: Pubkey, -} - -impl ExecuteOrderFallbackAccounts { - pub fn new( - current_state: &TestingEngineState, - payer_signer: &Pubkey, - fixture_accounts: &utils::account_fixtures::FixtureAccounts, - override_fast_market_order_address: Option, - ) -> Self { - let transfer_direction = current_state.base().transfer_direction; - let auction_accounts = current_state.auction_accounts().unwrap(); - let active_auction_state = current_state.auction_state().get_active_auction().unwrap(); - let fast_market_order_address = override_fast_market_order_address.unwrap_or_else(|| { - current_state - .fast_market_order() - .unwrap() - .fast_market_order_address - }); - let remote_token_messenger = match transfer_direction { - TransferDirection::FromEthereumToArbitrum => { - fixture_accounts.arbitrum_remote_token_messenger - } - TransferDirection::FromArbitrumToEthereum => { - fixture_accounts.ethereum_remote_token_messenger - } - _ => panic!("Unsupported transfer direction"), - }; - - Self { - signer: *payer_signer, - custodian: auction_accounts.custodian, - fast_market_order_address, - active_auction: active_auction_state.auction_address, - active_auction_custody_token: active_auction_state.auction_custody_token_address, - active_auction_config: auction_accounts.auction_config, - active_auction_best_offer_token: auction_accounts.offer_token, - initial_offer_token: auction_accounts.offer_token, - initial_participant: *payer_signer, - to_router_endpoint: auction_accounts.to_router_endpoint, - remote_token_messenger, - token_messenger: fixture_accounts.token_messenger, - } - } -} - -pub struct ExecuteOrderFallbackFixture { - pub cctp_message: Pubkey, - pub post_message_sequence: Pubkey, - pub post_message_message: Pubkey, - pub accounts: ExecuteOrderFallbackFixtureAccounts, -} - -pub struct ExecuteOrderFallbackFixtureAccounts { - pub local_token: Pubkey, - pub token_messenger: Pubkey, - pub remote_token_messenger: Pubkey, - pub token_messenger_minter_sender_authority: Pubkey, - pub token_messenger_minter_event_authority: Pubkey, - pub messenger_transmitter_config: Pubkey, - pub token_minter: Pubkey, - pub executor_token: Pubkey, -} - pub async fn execute_order_shimful( testing_context: &TestingContext, test_context: &mut ProgramTestContext, + current_state: &TestingEngineState, config: &ExecuteOrderInstructionConfig, - execute_order_fallback_accounts: &ExecuteOrderFallbackAccounts, - expected_error: Option<&ExpectedError>, -) -> Option { +) -> Option { let program_id = &testing_context.get_matching_engine_program_id(); let payer_signer = config .payer_signer .clone() .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); - let execute_order_fallback_fixture = create_execute_order_fallback_fixture( - testing_context, - config, - execute_order_fallback_accounts, - ); - let clock_id = solana_program::clock::Clock::id(); - let execute_order_ix_accounts = create_execute_order_shim_accounts( - execute_order_fallback_accounts, - &execute_order_fallback_fixture, - &clock_id, - ); - + let execute_order_ix_accounts = + ExecuteOrderShimAccountsOwned::new(testing_context, current_state, config); let execute_order_ix = ExecuteOrderCctpShim { program_id, - accounts: execute_order_ix_accounts, + accounts: execute_order_ix_accounts.as_ref(), } .instruction(); @@ -145,37 +57,245 @@ pub async fn execute_order_shimful( &[&payer_signer], recent_blockhash, ); + let expected_error = config.expected_error(); testing_context .execute_and_verify_transaction(test_context, transaction, expected_error) .await; if expected_error.is_none() { - Some(execute_order_fallback_fixture) + let order_executed_state = OrderExecutedState { + cctp_message: execute_order_ix_accounts.cctp_message, + post_message_sequence: Some(execute_order_ix_accounts.post_message_sequence), + post_message_message: Some(execute_order_ix_accounts.post_message_message), + cctp_message_bump: Some(execute_order_ix_accounts.cctp_message_bump), + actor_enum: config.actor_enum, + }; + Some(order_executed_state) } else { None } } -pub fn create_execute_order_fallback_fixture( +struct ExecuteOrderShimAccountsOwned { + pub signer: Pubkey, + pub cctp_message: Pubkey, + pub custodian: Pubkey, + pub fast_market_order: Pubkey, + pub active_auction: Pubkey, + pub active_auction_custody_token: Pubkey, + pub active_auction_config: Pubkey, + pub active_auction_best_offer_token: Pubkey, + pub initial_offer_token: Pubkey, + pub initial_participant: Pubkey, + pub to_router_endpoint: Pubkey, + pub executor_token: Pubkey, + pub post_message_shim_program: Pubkey, + pub post_message_sequence: Pubkey, + pub post_message_message: Pubkey, + pub cctp_deposit_for_burn_mint: Pubkey, + pub cctp_deposit_for_burn_token_messenger_minter_sender_authority: Pubkey, + pub cctp_deposit_for_burn_message_transmitter_config: Pubkey, + pub cctp_deposit_for_burn_token_messenger: Pubkey, + pub cctp_deposit_for_burn_remote_token_messenger: Pubkey, + pub cctp_deposit_for_burn_token_minter: Pubkey, + pub cctp_deposit_for_burn_local_token: Pubkey, + pub cctp_deposit_for_burn_token_messenger_minter_event_authority: Pubkey, + pub cctp_deposit_for_burn_token_messenger_minter_program: Pubkey, + pub cctp_deposit_for_burn_message_transmitter_program: Pubkey, + pub core_bridge_program: Pubkey, + pub core_bridge_config: Pubkey, + pub core_bridge_fee_collector: Pubkey, + pub post_message_shim_event_authority: Pubkey, + pub system_program: Pubkey, + pub token_program: Pubkey, + pub clock: Pubkey, + // Bump is passed to state for later use + pub cctp_message_bump: u8, +} + +impl ExecuteOrderShimAccountsOwned { + pub fn new( + testing_context: &TestingContext, + current_state: &TestingEngineState, + config: &ExecuteOrderInstructionConfig, + ) -> ExecuteOrderShimAccountsOwned { + let auction_accounts = current_state.auction_accounts().unwrap(); + let active_auction_state = current_state.auction_state().get_active_auction().unwrap(); + let custodian = current_state.custodian_address().unwrap(); + let fast_market_order_address = + config + .override_fast_market_order_address + .unwrap_or_else(|| { + current_state + .fast_market_order() + .unwrap() + .fast_market_order_address + }); + let payer_signer = config + .payer_signer + .clone() + .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); + + let program_id = &testing_context.get_matching_engine_program_id(); + let (cctp_message, cctp_message_bump) = Pubkey::find_program_address( + &[ + common::CCTP_MESSAGE_SEED_PREFIX, + &active_auction_state.auction_address.to_bytes(), + ], + program_id, + ); + + let cctp_deposit_for_burn_accounts = create_cctp_accounts(current_state, testing_context); + let post_message_sequence = wormhole_svm_definitions::find_emitter_sequence_address( + &custodian, + &CORE_BRIDGE_PROGRAM_ID, + ) + .0; + let post_message_message = wormhole_svm_definitions::find_shim_message_address( + &custodian, + &POST_MESSAGE_SHIM_PROGRAM_ID, + ) + .0; + let solver = config.actor_enum.get_actor(&testing_context.testing_actors); + let executor_token = solver.token_account_address(&config.token_enum).unwrap(); + let active_auction = current_state.auction_state().get_active_auction().unwrap(); + ExecuteOrderShimAccountsOwned { + signer: payer_signer.pubkey(), // 0 + cctp_message, // 1 + custodian, // 2 + fast_market_order: fast_market_order_address, // 3 + active_auction: active_auction_state.auction_address, // 4 + active_auction_custody_token: active_auction_state.auction_custody_token_address, // 5 + active_auction_config: auction_accounts.auction_config, // 6 + active_auction_best_offer_token: active_auction.best_offer.offer_token, // 7 + executor_token, // 8 + initial_offer_token: active_auction.initial_offer.offer_token, // 9 + initial_participant: active_auction.initial_offer.participant, // 10 + to_router_endpoint: auction_accounts.to_router_endpoint, // 11 + post_message_shim_program: POST_MESSAGE_SHIM_PROGRAM_ID, // 12 + post_message_sequence, // 13 + post_message_message, // 14 + cctp_deposit_for_burn_mint: cctp_deposit_for_burn_accounts.mint, // 15 + cctp_deposit_for_burn_token_messenger_minter_sender_authority: + cctp_deposit_for_burn_accounts.token_messenger_minter_sender_authority, // 16 + cctp_deposit_for_burn_message_transmitter_config: cctp_deposit_for_burn_accounts + .message_transmitter_config, // 17 + cctp_deposit_for_burn_token_messenger: cctp_deposit_for_burn_accounts.token_messenger, // 18 + cctp_deposit_for_burn_remote_token_messenger: cctp_deposit_for_burn_accounts + .remote_token_messenger, // 19 + cctp_deposit_for_burn_token_minter: cctp_deposit_for_burn_accounts.token_minter, // 20 + cctp_deposit_for_burn_local_token: cctp_deposit_for_burn_accounts.local_token, // 21 + cctp_deposit_for_burn_token_messenger_minter_event_authority: + cctp_deposit_for_burn_accounts.token_messenger_minter_event_authority, // 22 + cctp_deposit_for_burn_token_messenger_minter_program: cctp_deposit_for_burn_accounts + .token_messenger_minter_program, // 23 + cctp_deposit_for_burn_message_transmitter_program: cctp_deposit_for_burn_accounts + .message_transmitter_program, // 24 + core_bridge_program: CORE_BRIDGE_PROGRAM_ID, // 25 + core_bridge_config: CORE_BRIDGE_CONFIG, // 26 + core_bridge_fee_collector: CORE_BRIDGE_FEE_COLLECTOR, // 27 + post_message_shim_event_authority: POST_MESSAGE_SHIM_EVENT_AUTHORITY, // 28 + system_program: solana_program::system_program::ID, // 29 + token_program: spl_token::ID, // 30 + clock: solana_program::clock::Clock::id(), // 31 + cctp_message_bump, + } + } + + pub fn as_ref(&self) -> ExecuteOrderShimAccounts { + ExecuteOrderShimAccounts { + cctp_message: &self.cctp_message, + core_bridge_emitter_sequence: &self.post_message_sequence, + post_shim_message: &self.post_message_message, + signer: &self.signer, + custodian: &self.custodian, + fast_market_order: &self.fast_market_order, + active_auction: &self.active_auction, + active_auction_custody_token: &self.active_auction_custody_token, + active_auction_config: &self.active_auction_config, + active_auction_best_offer_token: &self.active_auction_best_offer_token, + executor_token: &self.executor_token, + initial_offer_token: &self.initial_offer_token, + initial_participant: &self.initial_participant, + to_router_endpoint: &self.to_router_endpoint, + post_message_shim_program: &self.post_message_shim_program, + cctp_deposit_for_burn_mint: &self.cctp_deposit_for_burn_mint, + cctp_deposit_for_burn_token_messenger_minter_sender_authority: &self + .cctp_deposit_for_burn_token_messenger_minter_sender_authority, + cctp_deposit_for_burn_message_transmitter_config: &self + .cctp_deposit_for_burn_message_transmitter_config, + cctp_deposit_for_burn_token_messenger: &self.cctp_deposit_for_burn_token_messenger, + cctp_deposit_for_burn_remote_token_messenger: &self + .cctp_deposit_for_burn_remote_token_messenger, + cctp_deposit_for_burn_token_minter: &self.cctp_deposit_for_burn_token_minter, + cctp_deposit_for_burn_local_token: &self.cctp_deposit_for_burn_local_token, + cctp_deposit_for_burn_token_messenger_minter_event_authority: &self + .cctp_deposit_for_burn_token_messenger_minter_event_authority, + cctp_deposit_for_burn_token_messenger_minter_program: &self + .cctp_deposit_for_burn_token_messenger_minter_program, + cctp_deposit_for_burn_message_transmitter_program: &self + .cctp_deposit_for_burn_message_transmitter_program, + core_bridge_program: &self.core_bridge_program, + core_bridge_config: &self.core_bridge_config, + core_bridge_fee_collector: &self.core_bridge_fee_collector, + post_message_shim_event_authority: &self.post_message_shim_event_authority, + system_program: &self.system_program, + token_program: &self.token_program, + clock: &self.clock, + } + } +} + +pub struct CctpAccounts { + pub mint: Pubkey, + pub token_messenger: Pubkey, + pub token_messenger_minter_sender_authority: Pubkey, + pub token_messenger_minter_event_authority: Pubkey, + pub message_transmitter_config: Pubkey, + pub token_minter: Pubkey, + pub local_token: Pubkey, + pub remote_token_messenger: Pubkey, + pub token_messenger_minter_program: Pubkey, + pub message_transmitter_program: Pubkey, +} + +impl Into for CctpAccounts { + fn into(self) -> CctpDepositForBurn { + CctpDepositForBurn { + mint: self.mint, + local_token: self.local_token, + token_messenger_minter_sender_authority: self.token_messenger_minter_sender_authority, + message_transmitter_config: self.message_transmitter_config, + token_messenger: self.token_messenger, + remote_token_messenger: self.remote_token_messenger, + token_minter: self.token_minter, + token_messenger_minter_event_authority: self.token_messenger_minter_event_authority, + message_transmitter_program: self.message_transmitter_program, + token_messenger_minter_program: self.token_messenger_minter_program, + } + } +} + +pub fn create_cctp_accounts( + current_state: &TestingEngineState, testing_context: &TestingContext, - config: &ExecuteOrderInstructionConfig, - execute_order_fallback_accounts: &ExecuteOrderFallbackAccounts, -) -> ExecuteOrderFallbackFixture { - let program_id = &testing_context.get_matching_engine_program_id(); - let cctp_message = Pubkey::find_program_address( - &[ - common::CCTP_MESSAGE_SEED_PREFIX, - &execute_order_fallback_accounts.active_auction.to_bytes(), - ], - program_id, - ) - .0; +) -> CctpAccounts { + let transfer_direction = current_state.base().transfer_direction; + let fixture_accounts = testing_context.get_fixture_accounts().unwrap(); + let remote_token_messenger = match transfer_direction { + TransferDirection::FromEthereumToArbitrum => { + fixture_accounts.arbitrum_remote_token_messenger + } + TransferDirection::FromArbitrumToEthereum => { + fixture_accounts.ethereum_remote_token_messenger + } + _ => panic!("Unsupported transfer direction"), + }; let token_messenger_minter_sender_authority = Pubkey::find_program_address(&[b"sender_authority"], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; - let messenger_transmitter_config = + let message_transmitter_config = Pubkey::find_program_address(&[b"message_transmitter"], &MESSAGE_TRANSMITTER_PROGRAM_ID).0; let token_messenger = Pubkey::find_program_address(&[b"token_messenger"], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; - let remote_token_messenger = execute_order_fallback_accounts.remote_token_messenger; let token_minter = Pubkey::find_program_address(&[b"token_minter"], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; let local_token = Pubkey::find_program_address( @@ -184,117 +304,17 @@ pub fn create_execute_order_fallback_fixture( ) .0; let token_messenger_minter_event_authority = - &Pubkey::find_program_address(&[EVENT_AUTHORITY_SEED], &TOKEN_MESSENGER_MINTER_PROGRAM_ID) - .0; - let post_message_sequence = wormhole_svm_definitions::find_emitter_sequence_address( - &execute_order_fallback_accounts.custodian, - &CORE_BRIDGE_PROGRAM_ID, - ) - .0; - let post_message_message = wormhole_svm_definitions::find_shim_message_address( - &execute_order_fallback_accounts.custodian, - &POST_MESSAGE_SHIM_PROGRAM_ID, - ) - .0; - let solver = config.actor_enum.get_actor(&testing_context.testing_actors); - let executor_token = solver.token_account_address(&config.token_enum).unwrap(); - ExecuteOrderFallbackFixture { - cctp_message, - post_message_sequence, - post_message_message, - accounts: ExecuteOrderFallbackFixtureAccounts { - local_token, - token_messenger, - remote_token_messenger, - token_messenger_minter_sender_authority, - token_messenger_minter_event_authority: *token_messenger_minter_event_authority, - messenger_transmitter_config, - token_minter, - executor_token, - }, - } -} - -pub fn create_execute_order_shim_accounts<'ix>( - execute_order_fallback_accounts: &'ix ExecuteOrderFallbackAccounts, - execute_order_fallback_fixture: &'ix ExecuteOrderFallbackFixture, - clock_id: &'ix Pubkey, -) -> ExecuteOrderShimAccounts<'ix> { - ExecuteOrderShimAccounts { - signer: &execute_order_fallback_accounts.signer, // 0 - cctp_message: &execute_order_fallback_fixture.cctp_message, // 1 - custodian: &execute_order_fallback_accounts.custodian, // 2 - fast_market_order: &execute_order_fallback_accounts.fast_market_order_address, // 3 - active_auction: &execute_order_fallback_accounts.active_auction, // 4 - active_auction_custody_token: &execute_order_fallback_accounts.active_auction_custody_token, // 5 - active_auction_config: &execute_order_fallback_accounts.active_auction_config, // 6 - active_auction_best_offer_token: &execute_order_fallback_accounts - .active_auction_best_offer_token, // 7 - executor_token: &execute_order_fallback_fixture.accounts.executor_token, // 8 - initial_offer_token: &execute_order_fallback_accounts.initial_offer_token, // 9 - initial_participant: &execute_order_fallback_accounts.initial_participant, // 10 - to_router_endpoint: &execute_order_fallback_accounts.to_router_endpoint, // 11 - post_message_shim_program: &POST_MESSAGE_SHIM_PROGRAM_ID, // 12 - core_bridge_emitter_sequence: &execute_order_fallback_fixture.post_message_sequence, // 13 - post_shim_message: &execute_order_fallback_fixture.post_message_message, // 14 - cctp_deposit_for_burn_mint: &USDC_MINT, // 15 - cctp_deposit_for_burn_token_messenger_minter_sender_authority: - &execute_order_fallback_fixture - .accounts - .token_messenger_minter_sender_authority, // 16 - cctp_deposit_for_burn_message_transmitter_config: &execute_order_fallback_fixture - .accounts - .messenger_transmitter_config, // 17 - cctp_deposit_for_burn_token_messenger: &execute_order_fallback_fixture - .accounts - .token_messenger, // 18 - cctp_deposit_for_burn_remote_token_messenger: &execute_order_fallback_fixture - .accounts - .remote_token_messenger, // 19 - cctp_deposit_for_burn_token_minter: &execute_order_fallback_fixture.accounts.token_minter, // 20 - cctp_deposit_for_burn_local_token: &execute_order_fallback_fixture.accounts.local_token, // 21 - cctp_deposit_for_burn_token_messenger_minter_event_authority: - &execute_order_fallback_fixture - .accounts - .token_messenger_minter_event_authority, // 22 - cctp_deposit_for_burn_token_messenger_minter_program: &TOKEN_MESSENGER_MINTER_PROGRAM_ID, // 23 - cctp_deposit_for_burn_message_transmitter_program: &MESSAGE_TRANSMITTER_PROGRAM_ID, // 24 - core_bridge_program: &CORE_BRIDGE_PROGRAM_ID, // 25 - core_bridge_config: &CORE_BRIDGE_CONFIG, // 26 - core_bridge_fee_collector: &CORE_BRIDGE_FEE_COLLECTOR, // 27 - post_message_shim_event_authority: &POST_MESSAGE_SHIM_EVENT_AUTHORITY, // 28 - system_program: &solana_program::system_program::ID, // 29 - token_program: &spl_token::ID, // 30 - clock: clock_id, // 31 + Pubkey::find_program_address(&[EVENT_AUTHORITY_SEED], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; + CctpAccounts { + mint: utils::constants::USDC_MINT, + token_messenger, + token_messenger_minter_sender_authority, + token_messenger_minter_event_authority, + message_transmitter_config, + token_minter, + local_token, + remote_token_messenger, + token_messenger_minter_program: TOKEN_MESSENGER_MINTER_PROGRAM_ID, + message_transmitter_program: MESSAGE_TRANSMITTER_PROGRAM_ID, } } - -pub async fn execute_order_shimful_test( - testing_context: &TestingContext, - test_context: &mut ProgramTestContext, - current_state: &TestingEngineState, - config: &ExecuteOrderInstructionConfig, -) -> Option { - let expected_error = config.expected_error(); - let fixture_accounts = testing_context - .get_fixture_accounts() - .expect("Pre-made fixture accounts not found"); - let payer_signer = config - .payer_signer - .clone() - .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); - let execute_order_fallback_accounts = ExecuteOrderFallbackAccounts::new( - current_state, - &payer_signer.pubkey(), - &fixture_accounts, - config.fast_market_order_address, - ); - execute_order_shimful( - testing_context, - test_context, - config, - &execute_order_fallback_accounts, - expected_error, - ) - .await -} diff --git a/solana/modules/matching-engine-testing/tests/shimful/shims_settle_auction_none.rs b/solana/modules/matching-engine-testing/tests/shimful/shims_settle_auction_none.rs new file mode 100644 index 00000000..d3778803 --- /dev/null +++ b/solana/modules/matching-engine-testing/tests/shimful/shims_settle_auction_none.rs @@ -0,0 +1,248 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::spl_token; +use matching_engine::{ + fallback::{ + settle_auction_none_cctp::{ + SettleAuctionNoneCctpShimAccounts, SettleAuctionNoneCctpShimData, + }, + FallbackMatchingEngineInstruction, + }, + state::Auction, +}; +use solana_program::instruction::Instruction; +use solana_program_test::ProgramTestContext; +use solana_sdk::{signature::Signer, sysvar::SysvarId, transaction::Transaction}; +use wormhole_svm_definitions::solana::{ + CORE_BRIDGE_PROGRAM_ID, POST_MESSAGE_SHIM_EVENT_AUTHORITY, POST_MESSAGE_SHIM_PROGRAM_ID, +}; + +use crate::{ + testing_engine::{ + config::{InstructionConfig, SettleAuctionNoneInstructionConfig}, + setup::TestingContext, + state::{OrderPreparedState, TestingEngineState}, + }, + utils::{auction::AuctionState, CORE_BRIDGE_CONFIG, CORE_BRIDGE_FEE_COLLECTOR}, +}; + +use super::shims_execute_order::{create_cctp_accounts, CctpAccounts}; + +pub struct SettleAuctionNoneCctpShim<'ix> { + pub program_id: &'ix Pubkey, + pub accounts: SettleAuctionNoneCctpShimAccounts<'ix>, + pub data: SettleAuctionNoneCctpShimData, +} + +impl SettleAuctionNoneCctpShim<'_> { + pub fn instruction(self) -> Instruction { + Instruction { + program_id: *self.program_id, + accounts: self.accounts.to_account_metas(), + data: FallbackMatchingEngineInstruction::SettleAuctionNoneCctp(self.data).to_vec(), + } + } +} + +pub async fn settle_auction_none_shimful( + testing_context: &TestingContext, + test_context: &mut ProgramTestContext, + current_state: &TestingEngineState, + config: &SettleAuctionNoneInstructionConfig, +) -> AuctionState { + let payer_signer = &config + .payer_signer + .clone() + .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); + + let settle_auction_none_cctp_accounts = + create_settle_auction_none_cctp_shimful_accounts(testing_context, current_state, config); + let settle_auction_none_cctp_data = settle_auction_none_cctp_accounts.bumps; + + let settle_auction_none_cctp_ix = SettleAuctionNoneCctpShim { + program_id: &testing_context.get_matching_engine_program_id(), + accounts: settle_auction_none_cctp_accounts.as_ref(), + data: settle_auction_none_cctp_data, + } + .instruction(); + let last_blockhash = test_context.get_new_latest_blockhash().await.unwrap(); + let tx = Transaction::new_signed_with_payer( + &[settle_auction_none_cctp_ix], + Some(&payer_signer.pubkey()), + &[&payer_signer], + last_blockhash, + ); + testing_context + .execute_and_verify_transaction(test_context, tx, config.expected_error()) + .await; + if config.expected_error().is_some() { + return current_state.auction_state().clone(); + } + + AuctionState::Settled +} + +struct SettleAuctionNoneCctpShimAccountsOwned { + pub payer: Pubkey, + pub post_message_message: Pubkey, + pub post_message_sequence: Pubkey, + pub post_message_shim_event_authority: Pubkey, + pub post_message_shim_program: Pubkey, + pub cctp_message: Pubkey, + pub custodian: Pubkey, + pub fee_recipient_token: Pubkey, + pub closed_prepared_order_response_actor: Pubkey, + pub closed_prepared_order_response: Pubkey, + pub closed_prepared_order_response_custody_token: Pubkey, + pub auction: Pubkey, + pub cctp_mint: Pubkey, + pub cctp_local_token: Pubkey, + pub cctp_token_messenger_minter_event_authority: Pubkey, + pub cctp_remote_token_messenger: Pubkey, + pub cctp_token_messenger: Pubkey, + pub cctp_token_messenger_minter_sender_authority: Pubkey, + pub cctp_token_minter: Pubkey, + pub cctp_token_messenger_minter_program: Pubkey, + pub cctp_message_transmitter_config: Pubkey, + pub cctp_message_transmitter_program: Pubkey, + pub core_bridge_program: Pubkey, + pub core_bridge_fee_collector: Pubkey, + pub core_bridge_config: Pubkey, + pub token_program: Pubkey, + pub system_program: Pubkey, + pub clock: Pubkey, + pub rent: Pubkey, + pub bumps: SettleAuctionNoneCctpShimData, +} + +impl SettleAuctionNoneCctpShimAccountsOwned { + pub fn as_ref(&self) -> SettleAuctionNoneCctpShimAccounts { + SettleAuctionNoneCctpShimAccounts { + payer: &self.payer, + post_shim_message: &self.post_message_message, + core_bridge_emitter_sequence: &self.post_message_sequence, + post_message_shim_event_authority: &self.post_message_shim_event_authority, + post_message_shim_program: &self.post_message_shim_program, + cctp_message: &self.cctp_message, + custodian: &self.custodian, + fee_recipient_token: &self.fee_recipient_token, + closed_prepared_order_response_actor: &self.closed_prepared_order_response_actor, + closed_prepared_order_response: &self.closed_prepared_order_response, + closed_prepared_order_response_custody_token: &self + .closed_prepared_order_response_custody_token, + auction: &self.auction, + cctp_mint: &self.cctp_mint, + cctp_local_token: &self.cctp_local_token, + cctp_token_messenger_minter_event_authority: &self + .cctp_token_messenger_minter_event_authority, + cctp_remote_token_messenger: &self.cctp_remote_token_messenger, + cctp_token_messenger: &self.cctp_token_messenger, + cctp_token_messenger_minter_sender_authority: &self + .cctp_token_messenger_minter_sender_authority, + cctp_token_minter: &self.cctp_token_minter, + cctp_token_messenger_minter_program: &self.cctp_token_messenger_minter_program, + cctp_message_transmitter_config: &self.cctp_message_transmitter_config, + cctp_message_transmitter_program: &self.cctp_message_transmitter_program, + core_bridge_program: &self.core_bridge_program, + core_bridge_fee_collector: &self.core_bridge_fee_collector, + core_bridge_config: &self.core_bridge_config, + token_program: &self.token_program, + system_program: &self.system_program, + clock: &self.clock, + rent: &self.rent, + } + } +} + +fn create_settle_auction_none_cctp_shimful_accounts( + testing_context: &TestingContext, + current_state: &TestingEngineState, + config: &SettleAuctionNoneInstructionConfig, +) -> SettleAuctionNoneCctpShimAccountsOwned { + let payer_signer = &config + .payer_signer + .clone() + .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); + + let order_prepared_state = current_state.order_prepared().unwrap(); + let OrderPreparedState { + prepared_order_response_address, + prepared_custody_token, + base_fee_token, + prepared_by, + } = *order_prepared_state; + + let custodian = current_state + .custodian_address() + .expect("Custodian address not found"); + + let fast_market_order = current_state.fast_market_order().unwrap().fast_market_order; + let fast_vaa_hash = fast_market_order.digest(); + let (auction, auction_bump) = Pubkey::find_program_address( + &[Auction::SEED_PREFIX, fast_vaa_hash.as_ref()], + &testing_context.get_matching_engine_program_id(), + ); + + let (cctp_message, cctp_message_bump) = Pubkey::find_program_address( + &[common::CCTP_MESSAGE_SEED_PREFIX, &auction.to_bytes()], + &testing_context.get_matching_engine_program_id(), + ); + + let post_message_sequence = wormhole_svm_definitions::find_emitter_sequence_address( + &custodian, + &CORE_BRIDGE_PROGRAM_ID, + ) + .0; + let post_message_message = wormhole_svm_definitions::find_shim_message_address( + &custodian, + &POST_MESSAGE_SHIM_PROGRAM_ID, + ) + .0; + + let CctpAccounts { + mint, + local_token, + token_messenger_minter_event_authority, + remote_token_messenger, + token_messenger, + token_messenger_minter_sender_authority, + token_minter, + token_messenger_minter_program, + message_transmitter_config, + message_transmitter_program, + } = create_cctp_accounts(current_state, testing_context); + SettleAuctionNoneCctpShimAccountsOwned { + payer: payer_signer.pubkey(), + post_message_message, + post_message_sequence, + post_message_shim_event_authority: POST_MESSAGE_SHIM_EVENT_AUTHORITY, + post_message_shim_program: POST_MESSAGE_SHIM_PROGRAM_ID, + cctp_message, + custodian, + fee_recipient_token: base_fee_token, + closed_prepared_order_response_actor: prepared_by, + closed_prepared_order_response: prepared_order_response_address, + closed_prepared_order_response_custody_token: prepared_custody_token, + auction, + cctp_mint: mint, + cctp_local_token: local_token, + cctp_token_messenger_minter_event_authority: token_messenger_minter_event_authority, + cctp_remote_token_messenger: remote_token_messenger, + cctp_token_messenger: token_messenger, + cctp_token_messenger_minter_sender_authority: token_messenger_minter_sender_authority, + cctp_token_minter: token_minter, + cctp_token_messenger_minter_program: token_messenger_minter_program, + cctp_message_transmitter_config: message_transmitter_config, + cctp_message_transmitter_program: message_transmitter_program, + core_bridge_program: CORE_BRIDGE_PROGRAM_ID, + core_bridge_fee_collector: CORE_BRIDGE_FEE_COLLECTOR, + core_bridge_config: CORE_BRIDGE_CONFIG, + token_program: spl_token::ID, + system_program: solana_program::system_program::ID, + clock: solana_program::clock::Clock::id(), + rent: solana_program::rent::Rent::id(), + bumps: SettleAuctionNoneCctpShimData { + cctp_message_bump, + auction_bump, + }, + } +} diff --git a/solana/modules/matching-engine-testing/tests/shimless/execute_order.rs b/solana/modules/matching-engine-testing/tests/shimless/execute_order.rs index 7e01382c..38c21684 100644 --- a/solana/modules/matching-engine-testing/tests/shimless/execute_order.rs +++ b/solana/modules/matching-engine-testing/tests/shimless/execute_order.rs @@ -1,14 +1,13 @@ use std::rc::Rc; -use crate::testing_engine::config::{ExecuteOrderInstructionConfig, ExpectedError}; -use crate::testing_engine::setup::{TestingContext, TransferDirection}; -use crate::utils::account_fixtures::FixtureAccounts; +use crate::shimful::shims_execute_order::create_cctp_accounts; +use crate::testing_engine::config::{ExecuteOrderInstructionConfig, InstructionConfig}; +use crate::testing_engine::setup::TestingContext; +use crate::testing_engine::state::TestingEngineState; use crate::utils::auction::{AuctionAccounts, AuctionState}; use anchor_lang::prelude::*; use anchor_lang::{InstructionData, ToAccountMetas}; -use common::wormhole_cctp_solana::cctp::{ - MESSAGE_TRANSMITTER_PROGRAM_ID, TOKEN_MESSENGER_MINTER_PROGRAM_ID, -}; + use matching_engine::accounts::{CctpDepositForBurn, WormholePublishMessage}; use matching_engine::accounts::{ ExecuteFastOrderCctp as ExecuteOrderShimlessAccounts, LiquidityLayerVaa, LiveRouterEndpoint, @@ -28,12 +27,23 @@ pub struct ExecuteOrderShimlessFixture { pub fn create_execute_order_shimless_accounts( testing_context: &TestingContext, - fixture_accounts: &FixtureAccounts, auction_accounts: &AuctionAccounts, payer_signer: &Rc, auction_state: &AuctionState, - executor_token: Pubkey, + config: &ExecuteOrderInstructionConfig, + current_state: &TestingEngineState, ) -> ExecuteOrderShimlessAccounts { + let executor_token = config + .actor_enum + .get_actor(&testing_context.testing_actors) + .token_account_address(&config.token_enum) + .unwrap_or_else(|| { + auction_state + .get_active_auction() + .unwrap() + .best_offer + .offer_token + }); let active_auction_state = auction_state.get_active_auction().unwrap(); let active_auction_address = active_auction_state.auction_address; let active_auction_custody_token = active_auction_state.auction_custody_token_address; @@ -91,46 +101,8 @@ pub fn create_execute_order_shimless_accounts( clock: Clock::id(), rent: Rent::id(), }; - let token_messenger_minter_event_authority = - Pubkey::find_program_address(&[EVENT_AUTHORITY_SEED], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; - let local_token = Pubkey::find_program_address( - &[ - b"local_token", - &testing_context.get_usdc_mint_address().to_bytes(), - ], - &TOKEN_MESSENGER_MINTER_PROGRAM_ID, - ) - .0; - let token_messenger_minter_sender_authority = - Pubkey::find_program_address(&[b"sender_authority"], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; - let message_transmitter_config = - Pubkey::find_program_address(&[b"message_transmitter"], &MESSAGE_TRANSMITTER_PROGRAM_ID).0; - let token_messenger = - Pubkey::find_program_address(&[b"token_messenger"], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; - let remote_token_messenger = match testing_context.transfer_direction { - TransferDirection::FromEthereumToArbitrum => { - fixture_accounts.arbitrum_remote_token_messenger - } - TransferDirection::FromArbitrumToEthereum => { - fixture_accounts.ethereum_remote_token_messenger - } - _ => panic!("Unsupported transfer direction"), - }; - let token_minter = - Pubkey::find_program_address(&[b"token_minter"], &TOKEN_MESSENGER_MINTER_PROGRAM_ID).0; - let cctp = CctpDepositForBurn { - mint: testing_context.get_usdc_mint_address(), - local_token, - token_messenger_minter_sender_authority, - message_transmitter_config, - token_messenger, - remote_token_messenger, - token_minter, - token_messenger_minter_event_authority, - message_transmitter_program: MESSAGE_TRANSMITTER_PROGRAM_ID, - token_messenger_minter_program: TOKEN_MESSENGER_MINTER_PROGRAM_ID, - }; + let cctp = create_cctp_deposit_for_burn(current_state, testing_context); let event_authority = Pubkey::find_program_address( &[EVENT_AUTHORITY_SEED], &testing_context.get_matching_engine_program_id(), @@ -156,42 +128,30 @@ pub fn create_execute_order_shimless_accounts( pub async fn execute_order_shimless_test( testing_context: &TestingContext, test_context: &mut ProgramTestContext, + current_state: &TestingEngineState, config: &ExecuteOrderInstructionConfig, auction_accounts: &AuctionAccounts, - auction_state: &AuctionState, - expected_error: Option<&ExpectedError>, ) -> Option { let payer_signer = config .payer_signer .clone() .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); + let auction_state = current_state.auction_state(); + let expected_error = config.expected_error(); let slots_to_fast_forward = config.fast_forward_slots; if slots_to_fast_forward > 0 { crate::testing_engine::engine::fast_forward_slots(test_context, slots_to_fast_forward) .await; } - let executor_token = config - .actor_enum - .get_actor(&testing_context.testing_actors) - .token_account_address(&config.token_enum) - .unwrap_or_else(|| { - auction_state - .get_active_auction() - .unwrap() - .best_offer - .offer_token - }); - let fixture_accounts = testing_context - .get_fixture_accounts() - .expect("Fixture accounts not found"); + let execute_order_accounts: ExecuteOrderShimlessAccounts = create_execute_order_shimless_accounts( testing_context, - &fixture_accounts, auction_accounts, &payer_signer, auction_state, - executor_token, + config, + current_state, ); let execute_order_instruction_data = ExecuteOrderShimlessInstruction {}.data(); let execute_order_ix = Instruction { @@ -219,3 +179,11 @@ pub async fn execute_order_shimless_test( None } } + +pub fn create_cctp_deposit_for_burn( + current_state: &TestingEngineState, + testing_context: &TestingContext, +) -> CctpDepositForBurn { + let cctp_accounts = create_cctp_accounts(current_state, testing_context); + cctp_accounts.into() +} diff --git a/solana/modules/matching-engine-testing/tests/shimless/mod.rs b/solana/modules/matching-engine-testing/tests/shimless/mod.rs index 62fc2076..f3719128 100644 --- a/solana/modules/matching-engine-testing/tests/shimless/mod.rs +++ b/solana/modules/matching-engine-testing/tests/shimless/mod.rs @@ -6,3 +6,4 @@ pub mod make_offer; pub mod pause_custodian; pub mod prepare_order_response; pub mod settle_auction; +pub mod settle_auction_none; \ No newline at end of file diff --git a/solana/modules/matching-engine-testing/tests/shimless/settle_auction.rs b/solana/modules/matching-engine-testing/tests/shimless/settle_auction.rs index 68f89044..fdd0cbf6 100644 --- a/solana/modules/matching-engine-testing/tests/shimless/settle_auction.rs +++ b/solana/modules/matching-engine-testing/tests/shimless/settle_auction.rs @@ -61,7 +61,7 @@ pub async fn settle_auction_complete( prepared_order_response_address, prepared_custody_token, base_fee_token, - actor_enum: _, + prepared_by: _, } = *order_prepared_state; let matching_engine_program_id = testing_context.get_matching_engine_program_id(); diff --git a/solana/modules/matching-engine-testing/tests/shimless/settle_auction_none.rs b/solana/modules/matching-engine-testing/tests/shimless/settle_auction_none.rs new file mode 100644 index 00000000..36f35ee8 --- /dev/null +++ b/solana/modules/matching-engine-testing/tests/shimless/settle_auction_none.rs @@ -0,0 +1,173 @@ +use crate::testing_engine::config::{InstructionConfig, SettleAuctionNoneInstructionConfig}; +use crate::testing_engine::setup::TestingContext; +use crate::testing_engine::state::{OrderPreparedState, TestingEngineState}; +use crate::utils::auction::AuctionState; +use crate::utils::token_account::SplTokenEnum; +use anchor_lang::prelude::*; +use anchor_lang::{InstructionData, ToAccountMetas}; +use anchor_spl::token::spl_token; +use matching_engine::accounts::RequiredSysvars; +use matching_engine::accounts::{ + CheckedCustodian, ClosePreparedOrderResponse, + SettleAuctionNoneCctp as SettleAuctionNoneCctpAccounts, WormholePublishMessage, +}; +use matching_engine::instruction::SettleAuctionNoneCctp as SettleAuctionNoneCctpIx; +use matching_engine::state::{Auction, PreparedOrderResponse}; +use solana_program_test::ProgramTestContext; +use solana_sdk::instruction::Instruction; +use solana_sdk::signature::Signer; +use solana_sdk::sysvar::SysvarId; +use solana_sdk::transaction::Transaction; +use wormhole_svm_definitions::EVENT_AUTHORITY_SEED; + +use super::execute_order::create_cctp_deposit_for_burn; + +/// Settle an auction none shimless +pub async fn settle_auction_none_shimless( + testing_context: &TestingContext, + current_state: &TestingEngineState, + test_context: &mut ProgramTestContext, + config: &SettleAuctionNoneInstructionConfig, +) -> AuctionState { + let payer_signer = &config + .payer_signer + .clone() + .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); + + let settle_auction_none_cctp_accounts = create_settle_auction_none_cctp_shimless_accounts( + test_context, + testing_context, + current_state, + config, + ) + .await; + let settle_auction_none_ix = Instruction { + program_id: testing_context.get_matching_engine_program_id(), + accounts: settle_auction_none_cctp_accounts.to_account_metas(None), + data: SettleAuctionNoneCctpIx {}.data(), + }; + let tx = Transaction::new_signed_with_payer( + &[settle_auction_none_ix], + Some(&payer_signer.pubkey()), + &[&payer_signer], + testing_context + .get_new_latest_blockhash(test_context) + .await + .unwrap(), + ); + + testing_context + .execute_and_verify_transaction(test_context, tx, config.expected_error()) + .await; + if config.expected_error().is_some() { + return current_state.auction_state().clone(); + } + + AuctionState::Settled +} + +async fn create_settle_auction_none_cctp_shimless_accounts( + test_context: &mut ProgramTestContext, + testing_context: &TestingContext, + current_state: &TestingEngineState, + config: &SettleAuctionNoneInstructionConfig, +) -> SettleAuctionNoneCctpAccounts { + let payer = config + .payer_signer + .clone() + .unwrap_or_else(|| testing_context.testing_actors.payer_signer.clone()); + + let order_prepared_state = current_state.order_prepared().unwrap(); + let OrderPreparedState { + prepared_order_response_address, + prepared_custody_token, + base_fee_token: _, + prepared_by, + } = *order_prepared_state; + + let checked_custodian = CheckedCustodian { + custodian: current_state.custodian_address().unwrap(), + }; + + let prepared_order_response_data = test_context + .banks_client + .get_account(prepared_order_response_address) + .await + .unwrap() + .unwrap() + .data; + let prepared_order = + PreparedOrderResponse::try_deserialize(&mut &prepared_order_response_data[..]).unwrap(); + let auction_seeds = &[ + Auction::SEED_PREFIX, + &prepared_order.seeds.fast_vaa_hash.as_ref(), + ]; + let (auction, _bump) = Pubkey::find_program_address( + auction_seeds, + &testing_context.get_matching_engine_program_id(), + ); + let (core_message, _bump) = Pubkey::find_program_address( + &[common::CORE_MESSAGE_SEED_PREFIX, &auction.as_ref()], + &testing_context.get_matching_engine_program_id(), + ); + + let (cctp_message, _bump) = Pubkey::find_program_address( + &[common::CCTP_MESSAGE_SEED_PREFIX, &auction.to_bytes()], + &testing_context.get_matching_engine_program_id(), + ); + let close_prepare_order_response = ClosePreparedOrderResponse { + by: prepared_by, + custody_token: prepared_custody_token, + order_response: prepared_order_response_address, + }; + let emitter_sequence = wormhole_svm_definitions::find_emitter_sequence_address( + &checked_custodian.custodian, + &wormhole_svm_definitions::solana::CORE_BRIDGE_PROGRAM_ID, + ) + .0; + let wormhole_publish_message = WormholePublishMessage { + config: wormhole_svm_definitions::solana::CORE_BRIDGE_CONFIG, + emitter_sequence, + fee_collector: wormhole_svm_definitions::solana::CORE_BRIDGE_FEE_COLLECTOR, + core_bridge_program: wormhole_svm_definitions::solana::CORE_BRIDGE_PROGRAM_ID, + }; + + let cctp = create_cctp_deposit_for_burn(current_state, testing_context); + + let sysvars = RequiredSysvars { + clock: Clock::id(), + rent: Rent::id(), + }; + + let event_authority = Pubkey::find_program_address( + &[EVENT_AUTHORITY_SEED], + &testing_context.get_matching_engine_program_id(), + ) + .0; + + let spl_token_enum = current_state + .spl_token_enum() + .unwrap_or_else(|| SplTokenEnum::Usdc); + let fee_recipient_token = testing_context + .testing_actors + .fee_recipient + .token_account_address(&spl_token_enum) + .unwrap(); + + SettleAuctionNoneCctpAccounts { + payer: payer.pubkey(), + custodian: checked_custodian, + fee_recipient_token, + core_message, + cctp_message, + prepared: close_prepare_order_response, + auction, + wormhole: wormhole_publish_message, + cctp, + token_program: spl_token::ID, + system_program: solana_program::system_program::ID, + event_authority, + program: testing_context.get_matching_engine_program_id(), + sysvars, + } +} diff --git a/solana/modules/matching-engine-testing/tests/test_scenarios/prepare_order.rs b/solana/modules/matching-engine-testing/tests/test_scenarios/prepare_order.rs index 1452611f..6ae2097e 100644 --- a/solana/modules/matching-engine-testing/tests/test_scenarios/prepare_order.rs +++ b/solana/modules/matching-engine-testing/tests/test_scenarios/prepare_order.rs @@ -120,9 +120,6 @@ pub async fn test_prepare_order_shimless() { InstructionTrigger::CreateCctpRouterEndpoints( CreateCctpRouterEndpointsInstructionConfig::default(), ), - InstructionTrigger::InitializeFastMarketOrderShim( - InitializeFastMarketOrderShimInstructionConfig::default(), - ), InstructionTrigger::PlaceInitialOfferShimless(PlaceInitialOfferInstructionConfig::default()), InstructionTrigger::ExecuteOrderShimless(ExecuteOrderInstructionConfig::default()), InstructionTrigger::PrepareOrderShimless(PrepareOrderResponseInstructionConfig::default()), diff --git a/solana/modules/matching-engine-testing/tests/test_scenarios/settle_auction.rs b/solana/modules/matching-engine-testing/tests/test_scenarios/settle_auction.rs index 54fd7a43..1f2a7d47 100644 --- a/solana/modules/matching-engine-testing/tests/test_scenarios/settle_auction.rs +++ b/solana/modules/matching-engine-testing/tests/test_scenarios/settle_auction.rs @@ -239,6 +239,84 @@ pub async fn test_settle_auction_base_fee_token_not_best_offer_actor() { .await; } +/// Test settle auction none shim +#[tokio::test] +pub async fn test_settle_auction_none_shimful() { + let (mut test_context, prepared_order_state, testing_engine, _initial_balances) = + Box::pin(helpers::prepare_settle_auction_none_shimful()).await; + let instruction_triggers = vec![InstructionTrigger::SettleAuctionNoneShim( + SettleAuctionNoneInstructionConfig::default(), + )]; + testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(prepared_order_state), + ) + .await; +} + +/// Test settle auction none shimless +#[tokio::test] +pub async fn test_settle_auction_none_shimless() { + let (mut test_context, prepared_order_state, testing_engine, _initial_balances) = + Box::pin(helpers::prepare_settle_auction_none_cctp_shimless()).await; + let instruction_triggers = vec![InstructionTrigger::SettleAuctionNoneShimless( + SettleAuctionNoneInstructionConfig::default(), + )]; + testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(prepared_order_state), + ) + .await; +} + +/// Test that balance changes are comparable between shim and shimless +#[tokio::test] +pub async fn test_settle_auction_none_balance_changes_comparable() { + let balance_changes_shimful = { + let (mut test_context, prepared_order_state, testing_engine, initial_balances) = + Box::pin(helpers::prepare_settle_auction_none_shimful()).await; + let instruction_triggers = vec![InstructionTrigger::SettleAuctionNoneShim( + SettleAuctionNoneInstructionConfig::default(), + )]; + testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(prepared_order_state), + ) + .await; + let final_balances = testing_engine + .testing_context + .get_balances(&mut test_context) + .await; + BalanceChanges::from((&initial_balances, &final_balances)) + }; + let balance_changes_shimless = { + let (mut test_context, prepared_order_state, testing_engine, initial_balances) = + Box::pin(helpers::prepare_settle_auction_none_cctp_shimless()).await; + let instruction_triggers = vec![InstructionTrigger::SettleAuctionNoneShimless( + SettleAuctionNoneInstructionConfig::default(), + )]; + testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(prepared_order_state), + ) + .await; + let final_balances = testing_engine + .testing_context + .get_balances(&mut test_context) + .await; + BalanceChanges::from((&initial_balances, &final_balances)) + }; + helpers::compare_balance_changes(&balance_changes_shimful, &balance_changes_shimless); +} + /* Sad path tests section @@ -322,10 +400,39 @@ pub async fn test_settle_auction_non_existent() { .await; } +/// Test cannot settle auction none if place initial offer is made +#[tokio::test] +pub async fn test_cannot_settle_auction_none_shim_after_place_initial_offer() { + let (mut test_context, prepared_order_state, testing_engine, _initial_balances) = + Box::pin(helpers::prepare_settle_auction_none_shimful()).await; + let instruction_triggers = vec![ + InstructionTrigger::PlaceInitialOfferShim(PlaceInitialOfferInstructionConfig::default()), + InstructionTrigger::SettleAuctionNoneShim(SettleAuctionNoneInstructionConfig { + expected_error: Some(ExpectedError { + instruction_index: 0, + error_code: 0, + error_string: "Account In Use".to_string(), + }), + ..SettleAuctionNoneInstructionConfig::default() + }), + ]; + testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(prepared_order_state), + ) + .await; +} + /* Helper code */ mod helpers { + use solana_program_test::ProgramTestContext; + + use crate::testing_engine::{setup::Balances, state::TestingEngineState}; + use super::*; pub async fn balance_changes_shim() -> BalanceChanges { @@ -527,4 +634,124 @@ mod helpers { "Solver 0 balance change should be the same for both shim and shimless" ); } + + /// Prepares testing engine state for settle auction none shimful + /// Returns: + /// - ProgramTestContext + /// - TestingEngineState + /// - TestingEngine + /// - Initial balances + pub async fn prepare_settle_auction_none_shimful() -> ( + ProgramTestContext, + TestingEngineState, + TestingEngine, + Balances, + ) { + let transfer_direction = TransferDirection::FromEthereumToArbitrum; + let (testing_context, mut test_context) = setup_environment( + ShimMode::VerifyAndPostSignature, + transfer_direction, + Some(vec![VaaArgs::default()]), + ) + .await; + let testing_engine = TestingEngine::new(testing_context).await; + + let instruction_triggers = vec![ + InstructionTrigger::InitializeProgram(InitializeInstructionConfig::default()), + InstructionTrigger::CreateCctpRouterEndpoints( + CreateCctpRouterEndpointsInstructionConfig::default(), + ), + ]; + let initial_state_balances = testing_engine + .testing_context + .get_balances(&mut test_context) + .await; + let create_cctp_router_endpoints_state = testing_engine + .execute(&mut test_context, instruction_triggers, None) + .await; + + // This is just needed to get the router endpoint accounts when prepare order happens before place initial offer, it is not used for anything else + let fake_auction_accounts = AuctionAccounts::fake_auction_accounts( + &create_cctp_router_endpoints_state, + &testing_engine.testing_context, + ); + let instruction_triggers = vec![ + InstructionTrigger::InitializeFastMarketOrderShim( + InitializeFastMarketOrderShimInstructionConfig::default(), + ), + InstructionTrigger::PrepareOrderShim(PrepareOrderResponseInstructionConfig { + overwrite_auction_accounts: Some(fake_auction_accounts), + ..Default::default() + }), + ]; + let prepare_order_state = testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(create_cctp_router_endpoints_state), + ) + .await; + ( + test_context, + prepare_order_state, + testing_engine, + initial_state_balances, + ) + } + + pub async fn prepare_settle_auction_none_cctp_shimless() -> ( + ProgramTestContext, + TestingEngineState, + TestingEngine, + Balances, + ) { + let transfer_direction = TransferDirection::FromEthereumToArbitrum; + let (testing_context, mut test_context) = setup_environment( + ShimMode::None, + transfer_direction, + Some(vec![VaaArgs { + post_vaa: true, + ..VaaArgs::default() + }]), + ) + .await; + let testing_engine = TestingEngine::new(testing_context).await; + + let instruction_triggers = vec![ + InstructionTrigger::InitializeProgram(InitializeInstructionConfig::default()), + InstructionTrigger::CreateCctpRouterEndpoints( + CreateCctpRouterEndpointsInstructionConfig::default(), + ), + ]; + let initial_state_balances = testing_engine + .testing_context + .get_balances(&mut test_context) + .await; + let create_cctp_router_endpoints_state = testing_engine + .execute(&mut test_context, instruction_triggers, None) + .await; + let fake_auction_accounts = AuctionAccounts::fake_auction_accounts( + &create_cctp_router_endpoints_state, + &testing_engine.testing_context, + ); + let instruction_triggers = vec![InstructionTrigger::PrepareOrderShimless( + PrepareOrderResponseInstructionConfig { + overwrite_auction_accounts: Some(fake_auction_accounts), + ..Default::default() + }, + )]; + let prepare_order_state = testing_engine + .execute( + &mut test_context, + instruction_triggers, + Some(create_cctp_router_endpoints_state), + ) + .await; + ( + test_context, + prepare_order_state, + testing_engine, + initial_state_balances, + ) + } } diff --git a/solana/modules/matching-engine-testing/tests/testing_engine/config.rs b/solana/modules/matching-engine-testing/tests/testing_engine/config.rs index 942b2aa3..d120cc07 100644 --- a/solana/modules/matching-engine-testing/tests/testing_engine/config.rs +++ b/solana/modules/matching-engine-testing/tests/testing_engine/config.rs @@ -177,7 +177,7 @@ impl InstructionConfig for PrepareOrderResponseInstructionConfig { #[derive(Clone)] pub struct ExecuteOrderInstructionConfig { - pub fast_market_order_address: OverwriteCurrentState, + pub override_fast_market_order_address: OverwriteCurrentState, pub actor_enum: TestingActorEnum, pub token_enum: SplTokenEnum, pub vaa_index: usize, @@ -192,7 +192,7 @@ impl Default for ExecuteOrderInstructionConfig { Self { fast_forward_slots: 3, actor_enum: TestingActorEnum::default(), - fast_market_order_address: None, + override_fast_market_order_address: None, token_enum: SplTokenEnum::default(), vaa_index: 0, payer_signer: None, @@ -619,3 +619,19 @@ pub struct BalanceChange { pub usdc: i32, pub usdt: i32, } + +#[derive(Default)] +pub struct SettleAuctionNoneInstructionConfig { + pub payer_signer: Option>, + pub expected_error: Option, + pub expected_log_messages: Option>, +} + +impl InstructionConfig for SettleAuctionNoneInstructionConfig { + fn expected_error(&self) -> Option<&ExpectedError> { + self.expected_error.as_ref() + } + fn expected_log_messages(&self) -> Option<&Vec> { + self.expected_log_messages.as_ref() + } +} diff --git a/solana/modules/matching-engine-testing/tests/testing_engine/engine.rs b/solana/modules/matching-engine-testing/tests/testing_engine/engine.rs index a2d77a86..342a830d 100644 --- a/solana/modules/matching-engine-testing/tests/testing_engine/engine.rs +++ b/solana/modules/matching-engine-testing/tests/testing_engine/engine.rs @@ -58,6 +58,8 @@ pub enum InstructionTrigger { PrepareOrderShim(PrepareOrderResponseInstructionConfig), SettleAuction(SettleAuctionInstructionConfig), CloseFastMarketOrderShim(CloseFastMarketOrderShimInstructionConfig), + SettleAuctionNoneShim(SettleAuctionNoneInstructionConfig), + SettleAuctionNoneShimless(SettleAuctionNoneInstructionConfig), } pub enum VerificationTrigger { @@ -158,6 +160,8 @@ impl InstructionConfig for InstructionTrigger { Self::PrepareOrderShim(config) => config.expected_error(), Self::SettleAuction(config) => config.expected_error(), Self::CloseFastMarketOrderShim(config) => config.expected_error(), + Self::SettleAuctionNoneShim(config) => config.expected_error(), + Self::SettleAuctionNoneShimless(config) => config.expected_error(), } } fn expected_log_messages(&self) -> Option<&Vec> { @@ -175,6 +179,8 @@ impl InstructionConfig for InstructionTrigger { Self::PrepareOrderShimless(config) => config.expected_log_messages(), Self::SettleAuction(config) => config.expected_log_messages(), Self::CloseFastMarketOrderShim(config) => config.expected_log_messages(), + Self::SettleAuctionNoneShim(config) => config.expected_log_messages(), + Self::SettleAuctionNoneShimless(config) => config.expected_log_messages(), } } } @@ -339,6 +345,14 @@ impl TestingEngine { self.settle_auction(test_context, current_state, config) .await } + InstructionTrigger::SettleAuctionNoneShim(ref config) => { + self.settle_auction_none_shim(test_context, current_state, config) + .await + } + InstructionTrigger::SettleAuctionNoneShimless(ref config) => { + self.settle_auction_none_shimless(test_context, current_state, config) + .await + } }, ExecutionTrigger::Verification(trigger) => match **trigger { VerificationTrigger::VerifyAuctionState(expected_to_succeed) => { @@ -531,6 +545,7 @@ impl TestingEngine { auction_state: current_state.auction_state().clone(), auction_accounts: current_state.auction_accounts().cloned(), order_prepared: current_state.order_prepared().cloned(), + order_executed: current_state.order_executed().cloned(), } } else { current_state.clone() @@ -727,7 +742,7 @@ impl TestingEngine { current_state: &TestingEngineState, config: &ExecuteOrderInstructionConfig, ) -> TestingEngineState { - let result = shimful::shims_execute_order::execute_order_shimful_test( + let result = shimful::shims_execute_order::execute_order_shimful( &self.testing_context, test_context, current_state, @@ -738,13 +753,7 @@ impl TestingEngine { let auction_accounts = current_state .auction_accounts() .expect("Auction accounts not found"); - let order_executed_fallback_fixture = result.unwrap(); - let order_executed_state = OrderExecutedState { - cctp_message: order_executed_fallback_fixture.cctp_message, - post_message_sequence: Some(order_executed_fallback_fixture.post_message_sequence), - post_message_message: Some(order_executed_fallback_fixture.post_message_message), - actor_enum: config.actor_enum, - }; + let order_executed_state = result.unwrap(); TestingEngineState::OrderExecuted { base: current_state.base().clone(), initialized: current_state.initialized().unwrap().clone(), @@ -796,10 +805,9 @@ impl TestingEngine { let result = shimless::execute_order::execute_order_shimless_test( &self.testing_context, test_context, + current_state, config, &auction_accounts, - current_state.auction_state(), - config.expected_error(), ) .await; if config.expected_error.is_none() { @@ -808,6 +816,7 @@ impl TestingEngine { cctp_message: execute_order_fixture.cctp_message, post_message_sequence: None, post_message_message: None, + cctp_message_bump: None, actor_enum: config.actor_enum, }; TestingEngineState::OrderExecuted { @@ -849,12 +858,16 @@ impl TestingEngine { .expect("Auction accounts not found") }); let prepare_order_response_fixture = result.unwrap(); + let payer_signer = config + .payer_signer + .clone() + .unwrap_or_else(|| self.testing_context.testing_actors.payer_signer.clone()); let order_prepared_state = OrderPreparedState { prepared_order_response_address: prepare_order_response_fixture .prepared_order_response, prepared_custody_token: prepare_order_response_fixture.prepared_custody_token, base_fee_token: prepare_order_response_fixture.base_fee_token, - actor_enum: config.actor_enum, + prepared_by: payer_signer.pubkey(), }; TestingEngineState::OrderPrepared { base: current_state.base().clone(), @@ -877,15 +890,19 @@ impl TestingEngine { current_state: &TestingEngineState, config: &PrepareOrderResponseInstructionConfig, ) -> TestingEngineState { - let auction_accounts = current_state - .auction_accounts() - .expect("Auction accounts not found"); + let auction_accounts = config + .overwrite_auction_accounts + .as_ref() + .unwrap_or_else(|| { + current_state + .auction_accounts() + .expect("Auction accounts not found") + }); let solver_token_account = config .actor_enum .get_actor(&self.testing_context.testing_actors) .token_account_address(&config.token_enum) .expect("Token account does not exist for solver at index"); - println!("Base fee token address: {:?}", solver_token_account); let result = shimless::prepare_order_response::prepare_order_response( &self.testing_context, test_context, @@ -896,12 +913,16 @@ impl TestingEngine { .await; if config.expected_error.is_none() { let prepare_order_response_fixture = result.unwrap(); + let payer_signer = config + .payer_signer + .clone() + .unwrap_or_else(|| self.testing_context.testing_actors.payer_signer.clone()); let order_prepared_state = OrderPreparedState { prepared_order_response_address: prepare_order_response_fixture .prepared_order_response, prepared_custody_token: prepare_order_response_fixture.prepared_custody_token, base_fee_token: prepare_order_response_fixture.base_fee_token, - actor_enum: config.actor_enum, + prepared_by: payer_signer.pubkey(), }; TestingEngineState::OrderPrepared { base: current_state.base().clone(), @@ -947,6 +968,65 @@ impl TestingEngine { } } + /// Instruction trigger function for settling an auction none shim + async fn settle_auction_none_shim( + &self, + test_context: &mut ProgramTestContext, + current_state: &TestingEngineState, + config: &SettleAuctionNoneInstructionConfig, + ) -> TestingEngineState { + let auction_state = shimful::shims_settle_auction_none::settle_auction_none_shimful( + &self.testing_context, + test_context, + current_state, + config, + ) + .await; + match auction_state { + AuctionState::Settled => TestingEngineState::AuctionSettled { + base: current_state.base().clone(), + initialized: current_state.initialized().unwrap().clone(), + router_endpoints: current_state.router_endpoints().unwrap().clone(), + auction_state: current_state.auction_state().clone(), + fast_market_order: current_state.fast_market_order().cloned(), + order_prepared: current_state.order_prepared().unwrap().clone(), + auction_accounts: current_state.auction_accounts().cloned(), + order_executed: current_state.order_executed().cloned(), + }, + _ => current_state.clone(), + } + } + + /// Instruction trigger function for settling an auction none shimless + async fn settle_auction_none_shimless( + &self, + test_context: &mut ProgramTestContext, + current_state: &TestingEngineState, + config: &SettleAuctionNoneInstructionConfig, + ) -> TestingEngineState { + let auction_state = shimless::settle_auction_none::settle_auction_none_shimless( + &self.testing_context, + current_state, + test_context, + config, + ) + .await; + match auction_state { + AuctionState::Settled => TestingEngineState::AuctionSettled { + base: current_state.base().clone(), + initialized: current_state.initialized().unwrap().clone(), + router_endpoints: current_state.router_endpoints().unwrap().clone(), + auction_state: current_state.auction_state().clone(), + fast_market_order: current_state.fast_market_order().cloned(), + order_prepared: current_state.order_prepared().unwrap().clone(), + auction_accounts: current_state.auction_accounts().cloned(), + order_executed: current_state.order_executed().cloned(), + }, + _ => current_state.clone(), + } + } + + /// Verfication trigger function for verifying an auction state async fn verify_auction_state( &self, test_context: &mut ProgramTestContext, diff --git a/solana/modules/matching-engine-testing/tests/testing_engine/state.rs b/solana/modules/matching-engine-testing/tests/testing_engine/state.rs index 5aee594b..9a5e8875 100644 --- a/solana/modules/matching-engine-testing/tests/testing_engine/state.rs +++ b/solana/modules/matching-engine-testing/tests/testing_engine/state.rs @@ -77,6 +77,7 @@ pub struct OfferImprovedState { #[derive(Clone)] pub struct OrderExecutedState { pub cctp_message: Pubkey, + pub cctp_message_bump: Option, pub post_message_sequence: Option, // Only set if shimful execution pub post_message_message: Option, // Only set if shimful execution pub actor_enum: TestingActorEnum, @@ -87,7 +88,8 @@ pub struct OrderPreparedState { pub prepared_order_response_address: Pubkey, pub prepared_custody_token: Pubkey, pub base_fee_token: Pubkey, - pub actor_enum: TestingActorEnum, + /// This will be the signer of the prepare order response instruction + pub prepared_by: Pubkey, } #[derive(Clone)] @@ -118,6 +120,7 @@ pub enum TestingEngineState { auction_state: AuctionState, auction_accounts: Option, order_prepared: Option, + order_executed: Option, }, InitialOfferPlaced { base: BaseState, @@ -345,6 +348,8 @@ impl TestingEngineState { match self { Self::AuctionSettled { order_executed, .. } => order_executed.as_ref(), Self::OrderExecuted { order_executed, .. } => Some(order_executed), + Self::FastMarketOrderClosed { order_executed, .. } => order_executed.as_ref(), + Self::FastMarketOrderAccountCreated { order_executed, .. } => order_executed.as_ref(), _ => None, } } @@ -383,6 +388,7 @@ impl TestingEngineState { auction_state: _, // Ignore the current auction state auction_accounts, order_prepared, + order_executed, } => Ok(Self::FastMarketOrderAccountCreated { base: base.clone(), initialized: initialized.clone(), @@ -392,6 +398,7 @@ impl TestingEngineState { auction_state: new_auction_state, // Use the new auction state auction_accounts: auction_accounts.clone(), order_prepared: order_prepared.clone(), + order_executed: order_executed.clone(), }), Self::InitialOfferPlaced { diff --git a/solana/modules/matching-engine-testing/tests/utils/auction.rs b/solana/modules/matching-engine-testing/tests/utils/auction.rs index 0fd82646..3ba9207a 100644 --- a/solana/modules/matching-engine-testing/tests/utils/auction.rs +++ b/solana/modules/matching-engine-testing/tests/utils/auction.rs @@ -23,7 +23,7 @@ use matching_engine::state::{Auction, AuctionConfig, AuctionInfo}; /// * `to_router_endpoint` - The address of the router endpoint for the destination chain /// * `custodian` - The address of the custodian /// * `usdc_mint` - The usdc mint address -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AuctionAccounts { pub posted_fast_vaa: Option, pub offer_token: Pubkey, diff --git a/solana/modules/matching-engine-testing/tests/utils/token_account.rs b/solana/modules/matching-engine-testing/tests/utils/token_account.rs index 6407e323..ab430340 100644 --- a/solana/modules/matching-engine-testing/tests/utils/token_account.rs +++ b/solana/modules/matching-engine-testing/tests/utils/token_account.rs @@ -153,7 +153,7 @@ pub fn read_keypair_from_file(filename: &str) -> Keypair { } /// Enum representing the different SPL token types -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum SplTokenEnum { Usdc, Usdt, diff --git a/solana/programs/matching-engine/src/fallback/processor/execute_order.rs b/solana/programs/matching-engine/src/fallback/processor/execute_order.rs index f81c9727..07daf4ef 100644 --- a/solana/programs/matching-engine/src/fallback/processor/execute_order.rs +++ b/solana/programs/matching-engine/src/fallback/processor/execute_order.rs @@ -466,8 +466,6 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> { .saturating_add(active_auction_info.security_deposit) .saturating_sub(user_reward); - msg!("Security deposit: {}", active_auction_info.security_deposit); - let penalized = penalty > 0; if penalized && active_auction_best_offer_token_account.key() != executor_token_account.key() { @@ -488,7 +486,6 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> { if utils::checked_deserialize_token_account(initial_offer_token_account, &common::USDC_MINT) .is_some() { - msg!("Initial offer token account exists"); if active_auction_best_offer_token_account.key() != initial_offer_token_account.key() { // Pay the auction initiator their fee. let transfer_ix = spl_token::instruction::transfer( @@ -500,10 +497,6 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> { init_auction_fee, ) .unwrap(); - msg!( - "Sending init auction fee {} to initial offer token account", - init_auction_fee - ); invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; // Because the initial offer token was paid this fee, we account for it here. remaining_custodied_amount = @@ -513,7 +506,6 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> { deposit_and_fee = deposit_and_fee .checked_add(init_auction_fee) .ok_or_else(|| MatchingEngineError::U64Overflow)?; - msg!("New deposit and fee: {}", deposit_and_fee); } } diff --git a/solana/programs/matching-engine/src/fallback/processor/initialize_fast_market_order.rs b/solana/programs/matching-engine/src/fallback/processor/initialize_fast_market_order.rs index 26023190..db74e592 100644 --- a/solana/programs/matching-engine/src/fallback/processor/initialize_fast_market_order.rs +++ b/solana/programs/matching-engine/src/fallback/processor/initialize_fast_market_order.rs @@ -194,6 +194,5 @@ pub fn initialize_fast_market_order( .copy_from_slice(fast_market_order_bytes); // End of fast market order account creation // ------------------------------------------------------------------------------------------------ - Ok(()) } diff --git a/solana/programs/matching-engine/src/fallback/processor/mod.rs b/solana/programs/matching-engine/src/fallback/processor/mod.rs index 6fcba815..e0462573 100644 --- a/solana/programs/matching-engine/src/fallback/processor/mod.rs +++ b/solana/programs/matching-engine/src/fallback/processor/mod.rs @@ -6,5 +6,6 @@ pub mod execute_order; pub mod initialize_fast_market_order; pub mod place_initial_offer; pub mod prepare_order_response; +pub mod settle_auction_none_cctp; pub mod helpers; diff --git a/solana/programs/matching-engine/src/fallback/processor/process_instruction.rs b/solana/programs/matching-engine/src/fallback/processor/process_instruction.rs index 7e88c765..1433bfdf 100644 --- a/solana/programs/matching-engine/src/fallback/processor/process_instruction.rs +++ b/solana/programs/matching-engine/src/fallback/processor/process_instruction.rs @@ -6,6 +6,9 @@ use super::initialize_fast_market_order::{ use super::place_initial_offer::{place_initial_offer_cctp_shim, PlaceInitialOfferCctpShimData}; use super::prepare_order_response::prepare_order_response_cctp_shim; use super::prepare_order_response::PrepareOrderResponseCctpShimData; +use super::settle_auction_none_cctp::{ + settle_auction_none_cctp_shim, SettleAuctionNoneCctpShimData, +}; use crate::ID; use anchor_lang::prelude::*; use wormhole_svm_definitions::make_anchor_discriminator; @@ -21,6 +24,8 @@ impl<'ix> FallbackMatchingEngineInstruction<'ix> { make_anchor_discriminator(b"global:execute_order_cctp_shim"); pub const PREPARE_ORDER_RESPONSE_CCTP_SHIM_SELECTOR: [u8; 8] = make_anchor_discriminator(b"global:prepare_order_response_cctp_shim"); + pub const SETTLE_AUCTION_NONE_CCTP_SELECTOR: [u8; 8] = + make_anchor_discriminator(b"global:settle_auction_none_cctp_shim"); } pub enum FallbackMatchingEngineInstruction<'ix> { @@ -29,6 +34,7 @@ pub enum FallbackMatchingEngineInstruction<'ix> { PlaceInitialOfferCctpShim(&'ix PlaceInitialOfferCctpShimData), ExecuteOrderCctpShim, PrepareOrderResponseCctpShim(PrepareOrderResponseCctpShimData), + SettleAuctionNoneCctp(SettleAuctionNoneCctpShimData), } pub fn process_instruction( @@ -57,6 +63,9 @@ pub fn process_instruction( FallbackMatchingEngineInstruction::PrepareOrderResponseCctpShim(data) => { prepare_order_response_cctp_shim(accounts, data) } + FallbackMatchingEngineInstruction::SettleAuctionNoneCctp(data) => { + settle_auction_none_cctp_shim(accounts, data) + } } } @@ -87,6 +96,11 @@ impl<'ix> FallbackMatchingEngineInstruction<'ix> { PrepareOrderResponseCctpShimData::from_bytes(&instruction_data[8..]).unwrap(), )) } + FallbackMatchingEngineInstruction::SETTLE_AUCTION_NONE_CCTP_SELECTOR => { + Some(Self::SettleAuctionNoneCctp( + SettleAuctionNoneCctpShimData::from_bytes(&instruction_data[8..]).unwrap(), + )) + } _ => None, } } @@ -159,6 +173,19 @@ impl FallbackMatchingEngineInstruction<'_> { ); out.extend_from_slice(&data_slice); + out + } + Self::SettleAuctionNoneCctp(data) => { + let data_slice = data.to_bytes(); + let total_capacity = 8_usize.saturating_add(data_slice.len()); // 8 for the selector, plus the data length + + let mut out = Vec::with_capacity(total_capacity); + + out.extend_from_slice( + &FallbackMatchingEngineInstruction::SETTLE_AUCTION_NONE_CCTP_SELECTOR, + ); + out.extend_from_slice(&data_slice); + out } } diff --git a/solana/programs/matching-engine/src/fallback/processor/settle_auction_none_cctp.rs b/solana/programs/matching-engine/src/fallback/processor/settle_auction_none_cctp.rs new file mode 100644 index 00000000..56b91286 --- /dev/null +++ b/solana/programs/matching-engine/src/fallback/processor/settle_auction_none_cctp.rs @@ -0,0 +1,509 @@ +use crate::ID; +use anchor_lang::{prelude::*, Discriminator}; +use anchor_spl::token::{spl_token, TokenAccount}; +use common::wormhole_io::TypePrefixedPayload; +use solana_program::program::invoke_signed_unchecked; + +use crate::{ + error::MatchingEngineError, + events::AuctionSettled, + processor::SettledNone, + state::{Auction, AuctionStatus, Custodian, MessageProtocol, PreparedOrderResponse}, +}; + +use super::{ + burn_and_post::{burn_and_post, PostMessageAccounts, PostMessageDerivedAccounts}, + helpers::{create_account_reliably, require_min_account_infos_len}, +}; + +#[derive(Copy, Clone)] +pub struct SettleAuctionNoneCctpShimData { + pub cctp_message_bump: u8, + pub auction_bump: u8, +} + +impl SettleAuctionNoneCctpShimData { + pub fn from_bytes(data: &[u8]) -> Option { + let cctp_message_bump = data[0]; + let auction_bump = data[1]; + Some(Self { + cctp_message_bump, + auction_bump, + }) + } + + pub fn to_bytes(&self) -> [u8; 2] { + [self.cctp_message_bump, self.auction_bump] + } +} + +pub struct SettleAuctionNoneCctpShimAccounts<'ix> { + /// Payer of the account + pub payer: &'ix Pubkey, // 0 + /// Post shim message account + pub post_shim_message: &'ix Pubkey, // 1 + /// Core bridge emitter sequence account + pub core_bridge_emitter_sequence: &'ix Pubkey, // 2 + /// Post message shim event authority + pub post_message_shim_event_authority: &'ix Pubkey, // 3 + /// Post message shim program + pub post_message_shim_program: &'ix Pubkey, // 4 + /// Cctp message CHECK: Seeds must be \["cctp-msg", auction.key().as_ref()\]. + pub cctp_message: &'ix Pubkey, // 5 + /// Custodian account + pub custodian: &'ix Pubkey, // 6 + /// Fee recipient token + pub fee_recipient_token: &'ix Pubkey, // 7 + /// Closed prepared order response actor (closed_by) + pub closed_prepared_order_response_actor: &'ix Pubkey, // 8 + /// Closed prepared order response + pub closed_prepared_order_response: &'ix Pubkey, // 9 + /// Closed prepared order response custody token + pub closed_prepared_order_response_custody_token: &'ix Pubkey, // 10 + /// Auction account CHECK: Init if needed, Seeds must be \["auction", prepared.order_response.seeds.fast_vaa_hash.as_ref()\]. + pub auction: &'ix Pubkey, // 11 + /// Cctp mint (must be USDC mint) + pub cctp_mint: &'ix Pubkey, // 12 + /// Cctp token messenger minter sender authority + pub cctp_token_messenger_minter_sender_authority: &'ix Pubkey, // 13 + /// Cctp message transmitter config + pub cctp_message_transmitter_config: &'ix Pubkey, // 14 + /// Cctp token messenger + pub cctp_token_messenger: &'ix Pubkey, // 15 + /// Cctp remote token messenger + pub cctp_remote_token_messenger: &'ix Pubkey, // 16 + /// Cctp token minter + pub cctp_token_minter: &'ix Pubkey, // 17 + /// Cctp local token + pub cctp_local_token: &'ix Pubkey, // 18 + /// Cctp token messenger minter event authority + pub cctp_token_messenger_minter_event_authority: &'ix Pubkey, // 19 + /// Cctp token messenger minter program + pub cctp_token_messenger_minter_program: &'ix Pubkey, // 20 + /// Cctp message transmitter program + pub cctp_message_transmitter_program: &'ix Pubkey, // 21 + /// Core bridge program + pub core_bridge_program: &'ix Pubkey, // 22 + /// Core bridge fee collector + pub core_bridge_fee_collector: &'ix Pubkey, // 23 + /// Core bridge config + pub core_bridge_config: &'ix Pubkey, // 24 + /// Token program + pub token_program: &'ix Pubkey, // 25 + /// System program + pub system_program: &'ix Pubkey, // 26 + /// Clock + pub clock: &'ix Pubkey, // 27 + /// Rent + pub rent: &'ix Pubkey, // 28 +} + +impl<'ix> SettleAuctionNoneCctpShimAccounts<'ix> { + pub fn to_account_metas(&self) -> Vec { + vec![ + AccountMeta::new_readonly(*self.payer, true), // 0 + AccountMeta::new(*self.post_shim_message, false), // 1 + AccountMeta::new(*self.core_bridge_emitter_sequence, false), // 2 + AccountMeta::new_readonly(*self.post_message_shim_event_authority, false), // 3 + AccountMeta::new_readonly(*self.post_message_shim_program, false), // 4 + AccountMeta::new(*self.cctp_message, false), // 5 + AccountMeta::new(*self.custodian, false), // 6 + AccountMeta::new(*self.fee_recipient_token, false), // 7 + AccountMeta::new(*self.closed_prepared_order_response_actor, false), // 8 + AccountMeta::new_readonly(*self.closed_prepared_order_response, false), // 9 + AccountMeta::new(*self.closed_prepared_order_response_custody_token, false), // 10 + AccountMeta::new(*self.auction, false), // 11 + AccountMeta::new(*self.cctp_mint, false), // 12 + AccountMeta::new_readonly(*self.cctp_token_messenger_minter_sender_authority, false), // 13 + AccountMeta::new(*self.cctp_message_transmitter_config, false), // 14 + AccountMeta::new_readonly(*self.cctp_token_messenger, false), // 15 + AccountMeta::new_readonly(*self.cctp_remote_token_messenger, false), // 16 + AccountMeta::new(*self.cctp_token_minter, false), // 17 + AccountMeta::new(*self.cctp_local_token, false), // 18 + AccountMeta::new_readonly(*self.cctp_token_messenger_minter_event_authority, false), // 19 + AccountMeta::new_readonly(*self.cctp_token_messenger_minter_program, false), // 20 + AccountMeta::new_readonly(*self.cctp_message_transmitter_program, false), // 21 + AccountMeta::new_readonly(*self.core_bridge_program, false), // 22 + AccountMeta::new(*self.core_bridge_fee_collector, false), // 23 + AccountMeta::new(*self.core_bridge_config, false), // 24 + AccountMeta::new_readonly(*self.token_program, false), // 25 + AccountMeta::new_readonly(*self.system_program, false), // 26 + AccountMeta::new_readonly(*self.clock, false), // 27 + AccountMeta::new_readonly(*self.rent, false), // 28 + ] + } +} + +pub fn settle_auction_none_cctp_shim( + accounts: &[AccountInfo], + data: SettleAuctionNoneCctpShimData, +) -> Result<()> { + let program_id = &crate::ID; + require_min_account_infos_len(accounts, 29)?; + let payer = &accounts[0]; + let post_shim_message = &accounts[1]; + let core_bridge_emitter_sequence = &accounts[2]; + let _post_message_shim_event_authority = &accounts[3]; + let _post_message_shim_program = &accounts[4]; + let cctp_message = &accounts[5]; + let custodian = &accounts[6]; + let fee_recipient_token = &accounts[7]; // Who is this? + let closed_prepared_order_response_actor = &accounts[8]; + let closed_prepared_order_response = &accounts[9]; + let closed_prepared_order_response_custody_token = &accounts[10]; + let auction = &accounts[11]; // Will be created here + let cctp_mint = &accounts[12]; + let cctp_token_messenger_minter_sender_authority = &accounts[13]; + let cctp_message_transmitter_config = &accounts[14]; + let cctp_token_messenger = &accounts[15]; + let cctp_remote_token_messenger = &accounts[16]; + let cctp_token_minter = &accounts[17]; + let cctp_local_token = &accounts[18]; + let cctp_token_messenger_minter_event_authority = &accounts[19]; + let cctp_token_messenger_minter_program = &accounts[20]; + let cctp_message_transmitter_program = &accounts[21]; + let _core_bridge_program = &accounts[22]; + let _core_bridge_fee_collector = &accounts[23]; + let _core_bridge_config = &accounts[24]; + let token_program = &accounts[25]; + let system_program = &accounts[26]; + let _clock = &accounts[27]; + let _rent = &accounts[28]; + + let mut prepared_order_response_account = PreparedOrderResponse::try_deserialize( + &mut &closed_prepared_order_response.data.borrow_mut()[..], + )?; + let fee_recipient_token_account = + &TokenAccount::try_deserialize(&mut &fee_recipient_token.data.borrow_mut()[..])?; + let prepared_custody_token_account = &TokenAccount::try_deserialize( + &mut &closed_prepared_order_response_custody_token + .data + .borrow_mut()[..], + )?; + + let to_router_endpoint = prepared_order_response_account.to_endpoint; + let destination_cctp_domain = match to_router_endpoint.protocol { + MessageProtocol::Cctp { domain } => domain, + _ => return Err(MatchingEngineError::InvalidCctpEndpoint.into()), + }; + + let auction_key = auction.key(); + + + // Start of checks + // ------------------------------------------------------------------------------------------------ + + // Check cctp message is writable + if !cctp_message.is_writable { + msg!("Cctp message is not writable"); + return Err(MatchingEngineError::AccountNotWritable.into()) + .map_err(|e: Error| e.with_account_name("cctp_message")); + } + + // Check cctp message seeds are valid + let cctp_message_seeds = [ + common::CCTP_MESSAGE_SEED_PREFIX, + auction_key.as_ref(), + &[data.cctp_message_bump], + ]; + + let cctp_message_pda = Pubkey::create_program_address(&cctp_message_seeds, &ID) + .map_err(|_| MatchingEngineError::InvalidPda)?; + if cctp_message_pda != cctp_message.key() { + msg!("Cctp message seeds are invalid"); + return Err(ErrorCode::ConstraintSeeds.into()) + .map_err(|e: Error| e.with_pubkeys((cctp_message_pda, cctp_message.key()))); + }; + // Check custodian owner is the matching engine program and that it deserializes into a checked custodian + require_eq!(custodian.owner, &ID); + let checked_custodian = Custodian::try_deserialize(&mut &custodian.data.borrow_mut()[..])?; + // Check that the fee recipient token is the custodian's fee recipient token + require_eq!(fee_recipient_token.key(), checked_custodian.fee_recipient_token); + + // Check seeds of prepared order response are valid + let prepared_order_response_pda = Pubkey::create_program_address( + &[ + PreparedOrderResponse::SEED_PREFIX, + prepared_order_response_account.seeds.fast_vaa_hash.as_ref(), + &[prepared_order_response_account.seeds.bump], + ], + program_id, + ) + .map_err(|_| MatchingEngineError::InvalidPda)?; + if prepared_order_response_pda != closed_prepared_order_response.key() { + msg!("Prepared order response seeds are invalid"); + return Err(ErrorCode::ConstraintSeeds.into()).map_err(|e: Error| { + e.with_pubkeys(( + prepared_order_response_pda, + closed_prepared_order_response.key(), + )) + }); + }; + // Check seeds of prepared custody token are valid + { + let (prepared_custody_token_pda, _) = Pubkey::find_program_address( + &[ + crate::PREPARED_CUSTODY_TOKEN_SEED_PREFIX, + closed_prepared_order_response.key().as_ref(), + ], + program_id, + ); + if prepared_custody_token_pda != closed_prepared_order_response_custody_token.key() { + msg!("Prepared custody token seeds are invalid"); + return Err(ErrorCode::ConstraintSeeds.into()).map_err(|e: Error| { + e.with_pubkeys(( + prepared_custody_token_pda, + closed_prepared_order_response_custody_token.key(), + )) + }); + }; + } + + // Check prepared by is the same as the prepared by in the accounts + require_eq!( + prepared_order_response_account.prepared_by, + closed_prepared_order_response_actor.key() + ); + + // Check that custody token is a token account + let _checked_prepared_custody_token = Box::new(TokenAccount::try_deserialize( + &mut &closed_prepared_order_response_custody_token + .data + .borrow_mut()[..], + )?); + // Check seeds of auction are valid + let auction_seeds = [ + Auction::SEED_PREFIX, + prepared_order_response_account.seeds.fast_vaa_hash.as_ref(), + &[data.auction_bump], + ]; + let auction_pda = Pubkey::create_program_address(&auction_seeds, program_id) + .map_err(|_| MatchingEngineError::InvalidPda)?; + if auction_pda != auction.key() { + return Err(MatchingEngineError::InvalidPda.into()); + } + + // End of checks + // ------------------------------------------------------------------------------------------------ + + // Begin of initialisation of auction account + // ------------------------------------------------------------------------------------------------ + let auction_space = 8 + Auction::INIT_SPACE_NO_AUCTION; + + let auction_signer_seeds = &[&auction_seeds[..]]; + create_account_reliably( + &payer.key(), + &auction.key(), + auction.lamports(), + auction_space, + accounts, + program_id, + auction_signer_seeds, + )?; + // Borrow the account data mutably + let mut auction_data = auction + .try_borrow_mut_data() + .map_err(|_| MatchingEngineError::AccountNotWritable)?; + // Write the discriminator to the first 8 bytes + let discriminator = Auction::discriminator(); + auction_data[0..8].copy_from_slice(&discriminator); + let mut auction_to_write = + prepared_order_response_account.new_auction_placeholder(data.auction_bump); + let prepare_none_and_settle_fill_pubkeys = PrepareNoneAndSettleFillPubkeys { + prepared_order_response_key: &closed_prepared_order_response.key(), + prepared_order_response_custody_token: &closed_prepared_order_response_custody_token.key(), + fee_recipient_token_key: &fee_recipient_token.key(), + custodian_key: &custodian.key(), + }; + let SettledNone { + user_amount, + fill, + auction_settled_event: _, + } = prepare_none_and_settle_fill( + prepare_none_and_settle_fill_pubkeys, + &mut prepared_order_response_account, + &mut auction_to_write, + fee_recipient_token_account, + prepared_custody_token_account, + accounts, + )?; + let auction_bytes = auction_to_write + .try_to_vec() + .map_err(|_| MatchingEngineError::BorshDeserializationError)?; + auction_data[8..8_usize.saturating_add(auction_bytes.len())].copy_from_slice(&auction_bytes); + // ------------------------------------------------------------------------------------------------ + // End of initialisation of auction account + + // Begin of burning and posting the message + // ------------------------------------------------------------------------------------------------ + let post_message_accounts = PostMessageAccounts { + emitter: custodian.key(), + payer: payer.key(), + derived: PostMessageDerivedAccounts { + message: post_shim_message.key(), + sequence: core_bridge_emitter_sequence.key(), + }, + }; + burn_and_post( + CpiContext::new_with_signer( + cctp_token_messenger_minter_program.to_account_info(), + common::wormhole_cctp_solana::cpi::DepositForBurnWithCaller { + burn_token_owner: custodian.to_account_info(), + payer: payer.to_account_info(), + token_messenger_minter_sender_authority: + cctp_token_messenger_minter_sender_authority.to_account_info(), + burn_token: closed_prepared_order_response_custody_token.to_account_info(), + message_transmitter_config: cctp_message_transmitter_config.to_account_info(), + token_messenger: cctp_token_messenger.to_account_info(), + remote_token_messenger: cctp_remote_token_messenger.to_account_info(), + token_minter: cctp_token_minter.to_account_info(), + local_token: cctp_local_token.to_account_info(), + mint: cctp_mint.to_account_info(), + cctp_message: cctp_message.to_account_info(), + message_transmitter_program: cctp_message_transmitter_program.to_account_info(), + token_messenger_minter_program: cctp_token_messenger_minter_program + .to_account_info(), + token_program: token_program.to_account_info(), + system_program: system_program.to_account_info(), + event_authority: cctp_token_messenger_minter_event_authority.to_account_info(), + }, + &[ + Custodian::SIGNER_SEEDS, + &[ + common::CCTP_MESSAGE_SEED_PREFIX, + auction.key().as_ref(), + &[data.cctp_message_bump], + ], + ], + ), + common::wormhole_cctp_solana::cpi::BurnAndPublishArgs { + burn_source: None, + destination_caller: to_router_endpoint.address, + destination_cctp_domain, + amount: user_amount, + mint_recipient: to_router_endpoint.mint_recipient, + wormhole_message_nonce: common::WORMHOLE_MESSAGE_NONCE, + payload: fill.to_vec(), + }, + post_message_accounts, + accounts, + )?; + // ------------------------------------------------------------------------------------------------ + // End of burning and posting the message + + // Begin of closing the prepared order response + // ------------------------------------------------------------------------------------------------ + let close_token_account_ix = spl_token::instruction::close_account( + &spl_token::ID, + &closed_prepared_order_response_custody_token.key(), + &closed_prepared_order_response_actor.key(), + &custodian.key(), + &[], + )?; + invoke_signed_unchecked( + &close_token_account_ix, + accounts, + &[&Custodian::SIGNER_SEEDS], + )?; + // ------------------------------------------------------------------------------------------------ + // End of closing the prepared order response + + Ok(()) +} + +struct PrepareNoneAndSettleFillPubkeys<'ix> { + prepared_order_response_key: &'ix Pubkey, + prepared_order_response_custody_token: &'ix Pubkey, + fee_recipient_token_key: &'ix Pubkey, + custodian_key: &'ix Pubkey, +} + +// Rewrite of settle_none_and_prepare_fill +fn prepare_none_and_settle_fill<'ix>( + pubkeys: PrepareNoneAndSettleFillPubkeys<'ix>, + prepared_order_response: &'ix mut PreparedOrderResponse, + auction: &mut Auction, + fee_recipient_token: &'ix TokenAccount, + prepared_custody_token: &'ix TokenAccount, + accounts: &[AccountInfo], +) -> Result { + let PrepareNoneAndSettleFillPubkeys { + prepared_order_response_key, + prepared_order_response_custody_token, + fee_recipient_token_key, + custodian_key, + } = pubkeys; + let prepared_order_response_signer_seeds = &[ + PreparedOrderResponse::SEED_PREFIX, + prepared_order_response.seeds.fast_vaa_hash.as_ref(), + &[prepared_order_response.seeds.bump], + ]; + // Pay the `fee_recipient` the base fee and init auction fee. This ensures that the protocol + // relayer is paid for relaying slow VAAs (which requires posting the fast order VAA) that do + // not have an associated auction. + let fee = prepared_order_response + .base_fee + .saturating_add(prepared_order_response.init_auction_fee); + + let transfer_ix = spl_token::instruction::transfer( + &spl_token::ID, + prepared_order_response_custody_token, + fee_recipient_token_key, + prepared_order_response_key, + &[], + fee, + )?; + + invoke_signed_unchecked( + &transfer_ix, + accounts, + &[prepared_order_response_signer_seeds], + )?; + + // Set authority instruction + let set_authority_ix = spl_token::instruction::set_authority( + &spl_token::ID, + prepared_order_response_custody_token, + Some(custodian_key), + spl_token::instruction::AuthorityType::AccountOwner, + prepared_order_response_key, + &[], + )?; + + invoke_signed_unchecked( + &set_authority_ix, + accounts, + &[prepared_order_response_signer_seeds], + )?; + + auction.status = AuctionStatus::Settled { + fee, + total_penalty: None, + }; + + 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), + } + .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). + let redeemer_message = std::mem::take(&mut prepared_order_response.redeemer_message) + .try_into() + .unwrap(); + Ok(SettledNone { + user_amount: prepared_custody_token.amount.saturating_sub(fee), + fill: common::messages::Fill { + source_chain: prepared_order_response.source_chain, + order_sender: prepared_order_response.sender, + redeemer: prepared_order_response.redeemer, + redeemer_message, + }, + auction_settled_event, + }) +} 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 9c8233cc..0b1ac507 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 @@ -22,10 +22,10 @@ struct SettleNoneAndPrepareFill<'ctx, 'info> { token_program: &'ctx Program<'info, token::Token>, } -struct SettledNone { - user_amount: u64, - fill: Fill, - auction_settled_event: AuctionSettled, +pub struct SettledNone { + pub user_amount: u64, + pub fill: Fill, + pub auction_settled_event: AuctionSettled, } fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> Result {