From c02ab3ef936517c460a43f37f6482c85b4e825c5 Mon Sep 17 00:00:00 2001 From: Bengt Lofgren Date: Sun, 11 May 2025 22:46:48 +0100 Subject: [PATCH 1/3] refactored execute_fast_order fn to be reused --- .../src/fallback/processor/execute_order.rs | 205 +-------- .../auction/execute_fast_order/mod.rs | 420 ++++++++++-------- 2 files changed, 258 insertions(+), 367 deletions(-) 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 5d6d291a7..4696e7b7b 100644 --- a/solana/programs/matching-engine/src/fallback/processor/execute_order.rs +++ b/solana/programs/matching-engine/src/fallback/processor/execute_order.rs @@ -9,8 +9,8 @@ use solana_program::{instruction::Instruction, program::invoke_signed_unchecked} use crate::{ error::MatchingEngineError, + processor::{get_user_amount_and_new_status_and_penalized, ActiveAuctionAccountInfos}, state::{Auction, AuctionStatus, Custodian, MessageProtocol}, - utils::{self, auction::DepositPenalty}, ID, }; @@ -346,193 +346,28 @@ pub(super) fn process(accounts: &[AccountInfo]) -> Result<()> { // TODO: Do we have to verify the CCTP message transmitter program is passed // in? - //////////////////////////////////////////////////////////////////////////// - // - // TODO: This execute order logic has been taken from the original execute - // order instructions. We plan on using a helper method instead of copy- - // pasting the same logic here. - // - //////////////////////////////////////////////////////////////////////////// - // Prepare the execute order (get the user amount, fill, and order executed event) - let current_slot = Clock::get().unwrap().slot; - - // We extend the grace period for locally executed orders. Reserving a sequence number for - // the fast fill will most likely require an additional transaction, so this buffer allows - // the best offer participant to perform his duty without the risk of getting slashed by - // another executor. - let additional_grace_period = match active_auction.target_protocol { - MessageProtocol::Local { .. } => { - crate::EXECUTE_FAST_ORDER_LOCAL_ADDITIONAL_GRACE_PERIOD.into() - } - _ => None, + let active_auction_accounts = ActiveAuctionAccountInfos { + auction_best_offer_token: auction_best_offer_token_info.to_account_info(), + auction_executor_token: executor_token_info.to_account_info(), + auction_initial_offer_token: auction_initial_offer_token_info.to_account_info(), + auction_custody_token: auction_custody_info.to_account_info(), + active_auction: active_auction_info.to_account_info(), + auction_custodian: custodian_info.to_account_info(), }; - - let DepositPenalty { - penalty, - user_reward, - } = utils::auction::compute_deposit_penalty( - &auction_config, - active_auction_inner_info, - current_slot, - additional_grace_period, - ); - - let init_auction_fee = fast_market_order.init_auction_fee; - - let user_amount = active_auction_inner_info - .amount_in - .saturating_sub(active_auction_inner_info.offer_price) - .saturating_sub(init_auction_fee) - .saturating_add(user_reward); - - // Keep track of the remaining amount in the custody token account. Whatever remains will go - // to the executor. - let custody_token = TokenAccount::try_deserialize(&mut &auction_custody_info.data.borrow()[..])?; - let mut remaining_custodied_amount = custody_token.amount.saturating_sub(user_amount); - - // Offer price + security deposit was checked in placing the initial offer. - let mut deposit_and_fee = active_auction_inner_info - .offer_price - .saturating_add(active_auction_inner_info.security_deposit) - .saturating_sub(user_reward); - - let penalized = penalty > 0; - - if penalized && auction_best_offer_token_info.key != executor_token_info.key { - deposit_and_fee = deposit_and_fee.saturating_sub(penalty); - } - - // Need these seeds in order to transfer tokens and then set authority of auction custody token account to the custodian - let auction_signer_seeds = &[ - Auction::SEED_PREFIX, - active_auction.vaa_hash.as_ref(), - &[active_auction.bump], - ]; - - // If the initial offer token account doesn't exist anymore, we have nowhere to send the - // init auction fee. The executor will get these funds instead. - // - // We check that this is a legitimate token account. - if utils::checked_deserialize_token_account( - auction_initial_offer_token_info, - &common::USDC_MINT, - ) - .is_some() - { - if auction_best_offer_token_info.key() != auction_initial_offer_token_info.key() { - // Pay the auction initiator their fee. - let transfer_ix = spl_token::instruction::transfer( - &spl_token::ID, - &auction_custody_info.key(), - &auction_initial_offer_token_info.key(), - &active_auction_info.key(), - &[], - init_auction_fee, - ) - .unwrap(); - - 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 = - remaining_custodied_amount.saturating_sub(init_auction_fee); - } else { - // Add it to the reimbursement. - deposit_and_fee = deposit_and_fee - .checked_add(init_auction_fee) - .ok_or_else(|| MatchingEngineError::U64Overflow)?; - } - } - - // Return the security deposit and the fee to the highest bidder. - if auction_best_offer_token_info.key == executor_token_info.key { - // If the best offer token is equal to the executor token, just send whatever remains in - // the custody token account. - // - // NOTE: This will revert if the best offer token does not exist. But this will present - // an opportunity for another executor to execute this order and take what the best - // offer token would have received. - let transfer_ix = spl_token::instruction::transfer( - &spl_token::ID, - &auction_custody_info.key(), - &auction_best_offer_token_info.key(), - &active_auction_info.key(), - &[], - deposit_and_fee, - ) - .unwrap(); - msg!( - "Sending deposit and fee amount {} to best offer token account", - deposit_and_fee - ); - invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; - } else { - // Otherwise, send the deposit and fee to the best offer token. If the best offer token - // doesn't exist at this point (which would be unusual), we will reserve these funds - // for the executor token. - if utils::checked_deserialize_token_account( - auction_best_offer_token_info, - &common::USDC_MINT, - ) - .is_some() - { - let transfer_ix = spl_token::instruction::transfer( - &spl_token::ID, - &auction_custody_info.key(), - &auction_best_offer_token_info.key(), - &active_auction_info.key(), - &[], - deposit_and_fee, - ) - .unwrap(); - msg!( - "Sending deposit and fee {} to best offer token account", - deposit_and_fee - ); - invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; - remaining_custodied_amount = remaining_custodied_amount.saturating_sub(deposit_and_fee); - } - - // And pay the executor whatever remains in the auction custody token account. - if remaining_custodied_amount > 0 { - let instruction = spl_token::instruction::transfer( - &spl_token::ID, - auction_custody_info.key, - executor_token_info.key, - &active_auction_info.key(), - &[], - remaining_custodied_amount, - ) - .unwrap(); - msg!( - "Sending remaining custodied amount {} to executor token account", - remaining_custodied_amount - ); - invoke_signed_unchecked(&instruction, accounts, &[auction_signer_seeds])?; - } - } - - // Set the authority of the custody token account to the custodian. He will take over from - // here. - let set_authority_ix = spl_token::instruction::set_authority( - &spl_token::ID, - auction_custody_info.key, - Some(custodian_info.key), - spl_token::instruction::AuthorityType::AccountOwner, - active_auction_info.key, - &[], - ) - .unwrap(); - - invoke_signed_unchecked(&set_authority_ix, accounts, &[auction_signer_seeds])?; + let (user_amount, new_status, _penalized) = get_user_amount_and_new_status_and_penalized( + &active_auction, + &custody_token, + &auction_config, + fast_market_order.init_auction_fee, + active_auction_accounts, + accounts, + )?; // Set the active auction status - active_auction.status = AuctionStatus::Completed { - slot: current_slot, - execute_penalty: if penalized { penalty.into() } else { None }, - }; + active_auction.status = new_status; let active_auction_info_data: &mut [u8] = &mut active_auction_info.data.borrow_mut(); let mut active_auction_cursor = std::io::Cursor::new(active_auction_info_data); @@ -549,12 +384,6 @@ pub(super) fn process(accounts: &[AccountInfo]) -> Result<()> { .unwrap(), }; - //////////////////////////////////////////////////////////////////////////// - // - // TODO: See above TODO. This is the end of the copy-pasted logic. - // - //////////////////////////////////////////////////////////////////////////// - // TODO: Write test that passes in random keypair for CCTP message account // to show that not having to check the PDA address is safe. let (_, new_cctp_message_bump) = Pubkey::find_program_address( diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs index 935596c07..2a46cbdcd 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs @@ -3,16 +3,17 @@ pub use cctp::*; mod local; pub use local::*; +use solana_program::program::invoke_signed_unchecked; use crate::{ composite::*, error::MatchingEngineError, events::OrderExecuted, - state::{Auction, AuctionStatus, MessageProtocol}, + state::{Auction, AuctionConfig, AuctionInfo, AuctionStatus, MessageProtocol}, utils::{self, auction::DepositPenalty}, }; use anchor_lang::prelude::*; -use anchor_spl::token; +use anchor_spl::token::{self, spl_token, TokenAccount}; use common::messages::{ raw::{LiquidityLayerMessage, MessageToVec}, Fill, @@ -42,186 +43,40 @@ fn handle_execute_fast_order<'info>( .unwrap() .to_fast_market_order_unchecked(); - let (user_amount, new_status, order_executed_event) = { - let auction_info = auction.info.as_ref().unwrap(); - let current_slot = Clock::get().unwrap().slot; - - // We extend the grace period for locally executed orders. Reserving a sequence number for - // the fast fill will most likely require an additional transaction, so this buffer allows - // the best offer participant to perform his duty without the risk of getting slashed by - // another executor. - let additional_grace_period = match auction.target_protocol { - MessageProtocol::Local { .. } => { - crate::EXECUTE_FAST_ORDER_LOCAL_ADDITIONAL_GRACE_PERIOD.into() - } - _ => None, - }; - - let DepositPenalty { - penalty, - user_reward, - } = utils::auction::compute_deposit_penalty( - config, - auction_info, - current_slot, - additional_grace_period, - ); - - let init_auction_fee = order.init_auction_fee(); - - let user_amount = auction_info - .amount_in - .saturating_sub(auction_info.offer_price) - .saturating_sub(init_auction_fee) - .saturating_add(user_reward); - - // Keep track of the remaining amount in the custody token account. Whatever remains will go - // to the executor. - let mut remaining_custodied_amount = custody_token.amount.saturating_sub(user_amount); - - // Offer price + security deposit was checked in placing the initial offer. - let mut deposit_and_fee = auction_info - .offer_price - .saturating_add(auction_info.security_deposit) - .saturating_sub(user_reward); - - let auction_signer_seeds = &[ - Auction::SEED_PREFIX, - auction.vaa_hash.as_ref(), - &[auction.bump], - ]; + let active_auction_account_infos = ActiveAuctionAccountInfos { + auction_best_offer_token: execute_order + .active_auction + .best_offer_token + .to_account_info(), + auction_executor_token: execute_order.executor_token.to_account_info(), + auction_initial_offer_token: execute_order.initial_offer_token.to_account_info(), + auction_custody_token: execute_order.active_auction.custody_token.to_account_info(), + active_auction: auction.to_account_info(), + auction_custodian: custodian.to_account_info(), + }; - let penalized = penalty > 0; + let accounts = &[ + auction.to_account_info(), + custody_token.to_account_info(), + config.to_account_info(), + executor_token.to_account_info(), + best_offer_token.to_account_info(), + initial_offer_token.to_account_info(), + fast_vaa.to_account_info(), + token_program.to_account_info(), + ]; - if penalized && best_offer_token.key() != executor_token.key() { - deposit_and_fee = deposit_and_fee.saturating_sub(penalty); - } + let (user_amount, new_status, penalized) = get_user_amount_and_new_status_and_penalized( + auction, + custody_token, + config, + order.init_auction_fee(), + active_auction_account_infos, + accounts, + )?; - // If the initial offer token account doesn't exist anymore, we have nowhere to send the - // init auction fee. The executor will get these funds instead. - // - // We check that this is a legitimate token account. - if utils::checked_deserialize_token_account(initial_offer_token, &common::USDC_MINT) - .is_some() - { - if best_offer_token.key() != initial_offer_token.key() { - // Pay the auction initiator their fee. - token::transfer( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::Transfer { - from: custody_token.to_account_info(), - to: initial_offer_token.to_account_info(), - authority: auction.to_account_info(), - }, - &[auction_signer_seeds], - ), - init_auction_fee, - )?; - - // Because the initial offer token was paid this fee, we account for it here. - remaining_custodied_amount = - remaining_custodied_amount.saturating_sub(init_auction_fee); - } else { - // Add it to the reimbursement. - deposit_and_fee = deposit_and_fee - .checked_add(init_auction_fee) - .ok_or_else(|| MatchingEngineError::U64Overflow)?; - } - } - - // Return the security deposit and the fee to the highest bidder. - // - if best_offer_token.key() == executor_token.key() { - // If the best offer token is equal to the executor token, just send whatever remains in - // the custody token account. - // - // NOTE: This will revert if the best offer token does not exist. But this will present - // an opportunity for another executor to execute this order and take what the best - // offer token would have received. - token::transfer( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::Transfer { - from: custody_token.to_account_info(), - to: best_offer_token.to_account_info(), - authority: auction.to_account_info(), - }, - &[auction_signer_seeds], - ), - remaining_custodied_amount, - )?; - } else { - // Otherwise, send the deposit and fee to the best offer token. If the best offer token - // doesn't exist at this point (which would be unusual), we will reserve these funds - // for the executor token. - if utils::checked_deserialize_token_account(best_offer_token, &common::USDC_MINT) - .is_some() - { - token::transfer( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::Transfer { - from: custody_token.to_account_info(), - to: best_offer_token.to_account_info(), - authority: auction.to_account_info(), - }, - &[auction_signer_seeds], - ), - deposit_and_fee, - )?; - - remaining_custodied_amount = - remaining_custodied_amount.saturating_sub(deposit_and_fee); - } - - // And pay the executor whatever remains in the auction custody token account. - if remaining_custodied_amount > 0 { - token::transfer( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::Transfer { - from: custody_token.to_account_info(), - to: executor_token.to_account_info(), - authority: auction.to_account_info(), - }, - &[auction_signer_seeds], - ), - remaining_custodied_amount, - )?; - } - } - - // Set the authority of the custody token account to the custodian. He will take over from - // here. - token::set_authority( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::SetAuthority { - current_authority: auction.to_account_info(), - account_or_mint: custody_token.to_account_info(), - }, - &[auction_signer_seeds], - ), - token::spl_token::instruction::AuthorityType::AccountOwner, - custodian.key().into(), - )?; - - ( - user_amount, - AuctionStatus::Completed { - slot: current_slot, - execute_penalty: if penalized { penalty.into() } else { None }, - }, - OrderExecuted { - fast_vaa_hash: auction.vaa_hash, - vaa: fast_vaa.key(), - source_chain: auction_info.source_chain, - target_protocol: auction.target_protocol, - penalized, - }, - ) - }; + let order_executed_event = + get_order_executed_event(auction, fast_vaa, auction.info.as_ref().unwrap(), penalized); // Set the auction status to completed. auction.status = new_status; @@ -240,3 +95,210 @@ fn handle_execute_fast_order<'info>( order_executed_event, }) } + +pub struct ActiveAuctionAccountInfos<'ix> { + pub auction_best_offer_token: AccountInfo<'ix>, + pub auction_executor_token: AccountInfo<'ix>, + pub auction_initial_offer_token: AccountInfo<'ix>, + pub auction_custody_token: AccountInfo<'ix>, + pub active_auction: AccountInfo<'ix>, + pub auction_custodian: AccountInfo<'ix>, +} + +pub fn get_user_amount_and_new_status_and_penalized<'ix>( + auction: &Auction, + custody_token: &TokenAccount, + auction_config: &AuctionConfig, + init_auction_fee: u64, + active_auction_account_infos: ActiveAuctionAccountInfos<'ix>, + accounts: &[AccountInfo], +) -> Result<(u64, AuctionStatus, bool)> { + let auction_info = auction.info.as_ref().unwrap(); + let current_slot = Clock::get().unwrap().slot; + + let ActiveAuctionAccountInfos { + auction_best_offer_token: active_auction_best_offer_token, + auction_executor_token: active_auction_executor_token, + auction_initial_offer_token: active_auction_initial_offer_token, + auction_custody_token: active_auction_custody_token, + active_auction, + auction_custodian: active_auction_custodian, + } = active_auction_account_infos; + + let additional_grace_period = match auction.target_protocol { + MessageProtocol::Local { .. } => { + crate::EXECUTE_FAST_ORDER_LOCAL_ADDITIONAL_GRACE_PERIOD.into() + } + _ => None, + }; + + let DepositPenalty { + penalty, + user_reward, + } = utils::auction::compute_deposit_penalty( + &auction_config.parameters, + auction_info, + current_slot, + additional_grace_period, + ); + + let user_amount = auction_info + .amount_in + .saturating_sub(auction_info.offer_price) + .saturating_sub(init_auction_fee) + .saturating_add(user_reward); + let mut remaining_custodied_amount = custody_token.amount.saturating_sub(user_amount); + + let mut deposit_and_fee = auction_info + .offer_price + .saturating_add(auction_info.security_deposit) + .saturating_sub(user_reward); + + let auction_signer_seeds = &[ + Auction::SEED_PREFIX, + auction.vaa_hash.as_ref(), + &[auction.bump], + ]; + + let penalized = penalty > 0; + + if penalized && active_auction_best_offer_token.key() != active_auction_executor_token.key() { + deposit_and_fee = deposit_and_fee.saturating_sub(penalty); + } + + if utils::checked_deserialize_token_account( + &active_auction_initial_offer_token, + &common::USDC_MINT, + ) + .is_some() + { + if active_auction_best_offer_token.key() != active_auction_initial_offer_token.key() { + // Pay the auction initiator their fee. + let transfer_ix = spl_token::instruction::transfer( + &spl_token::ID, + &active_auction_custody_token.key(), + &active_auction_initial_offer_token.key(), + &active_auction.key(), + &[], + init_auction_fee, + ) + .unwrap(); + + 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 = + remaining_custodied_amount.saturating_sub(init_auction_fee); + } else { + // Add it to the reimbursement. + deposit_and_fee = deposit_and_fee + .checked_add(init_auction_fee) + .ok_or_else(|| MatchingEngineError::U64Overflow)?; + } + } + + // Return the security deposit and the fee to the highest bidder. + if active_auction_best_offer_token.key() == active_auction_executor_token.key() { + // If the best offer token is equal to the executor token, just send whatever remains in + // the custody token account. + // + // NOTE: This will revert if the best offer token does not exist. But this will present + // an opportunity for another executor to execute this order and take what the best + // offer token would have received. + let transfer_ix = spl_token::instruction::transfer( + &spl_token::ID, + &active_auction_custody_token.key(), + &active_auction_best_offer_token.key(), + &active_auction.key(), + &[], + deposit_and_fee, + ) + .unwrap(); + msg!( + "Sending deposit and fee amount {} to best offer token account", + deposit_and_fee + ); + invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; + } else { + // Otherwise, send the deposit and fee to the best offer token. If the best offer token + // doesn't exist at this point (which would be unusual), we will reserve these funds + // for the executor token. + if utils::checked_deserialize_token_account( + &active_auction_best_offer_token, + &common::USDC_MINT, + ) + .is_some() + { + let transfer_ix = spl_token::instruction::transfer( + &spl_token::ID, + &active_auction_custody_token.key(), + &active_auction_best_offer_token.key(), + &active_auction.key(), + &[], + deposit_and_fee, + ) + .unwrap(); + msg!( + "Sending deposit and fee {} to best offer token account", + deposit_and_fee + ); + invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; + remaining_custodied_amount = remaining_custodied_amount.saturating_sub(deposit_and_fee); + } + + // And pay the executor whatever remains in the auction custody token account. + if remaining_custodied_amount > 0 { + let instruction = spl_token::instruction::transfer( + &spl_token::ID, + &active_auction_custody_token.key(), + &active_auction_executor_token.key(), + &active_auction.key(), + &[], + remaining_custodied_amount, + ) + .unwrap(); + msg!( + "Sending remaining custodied amount {} to executor token account", + remaining_custodied_amount + ); + invoke_signed_unchecked(&instruction, accounts, &[auction_signer_seeds])?; + } + } + + // Set the authority of the custody token account to the custodian. He will take over from + // here. + let set_authority_ix = spl_token::instruction::set_authority( + &spl_token::ID, + &active_auction_custody_token.key(), + Some(&active_auction_custodian.key()), + spl_token::instruction::AuthorityType::AccountOwner, + &active_auction.key(), + &[], + ) + .unwrap(); + + invoke_signed_unchecked(&set_authority_ix, accounts, &[auction_signer_seeds])?; + + Ok(( + user_amount, + AuctionStatus::Completed { + slot: current_slot, + execute_penalty: if penalized { penalty.into() } else { None }, + }, + penalized, + )) +} + +pub fn get_order_executed_event<'ix>( + auction: &Auction, + fast_vaa: &AccountInfo<'ix>, + auction_info: &AuctionInfo, + penalized: bool, +) -> OrderExecuted { + OrderExecuted { + fast_vaa_hash: auction.vaa_hash, + vaa: fast_vaa.key(), + source_chain: auction_info.source_chain, + target_protocol: auction.target_protocol, + penalized, + } +} From 823221ec71a81432346ee751f74a90ab798aed99 Mon Sep 17 00:00:00 2001 From: Bengt Lofgren Date: Tue, 13 May 2025 14:12:50 +0100 Subject: [PATCH 2/3] place initial offer helper function with reuse --- .../fallback/processor/place_initial_offer.rs | 10 ++++----- .../auction/execute_fast_order/mod.rs | 8 +++---- .../auction/offer/place_initial/cctp.rs | 22 +++++++++++++------ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/solana/programs/matching-engine/src/fallback/processor/place_initial_offer.rs b/solana/programs/matching-engine/src/fallback/processor/place_initial_offer.rs index 30868f236..aa50eecc8 100644 --- a/solana/programs/matching-engine/src/fallback/processor/place_initial_offer.rs +++ b/solana/programs/matching-engine/src/fallback/processor/place_initial_offer.rs @@ -6,6 +6,7 @@ use solana_program::{instruction::Instruction, program::invoke_signed_unchecked} use crate::{ error::MatchingEngineError, + processor::calculate_security_deposit, state::{Auction, AuctionInfo, AuctionStatus, MessageProtocol}, ID, }; @@ -261,11 +262,10 @@ pub(super) fn process( // The total amount being transferred to the auction's custody token account // is the order's amount and auction participant's security deposit. - let security_deposit = fast_market_order.max_fee.saturating_add( - crate::utils::auction::compute_notional_security_deposit( - &auction_config, - fast_market_order.amount_in, - ), + let security_deposit = calculate_security_deposit( + fast_market_order.max_fee, + fast_market_order.amount_in, + &auction_config, ); let transfer_ix = spl_token::instruction::transfer( diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs index 2a46cbdcd..fa7b58114 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs @@ -105,12 +105,12 @@ pub struct ActiveAuctionAccountInfos<'ix> { pub auction_custodian: AccountInfo<'ix>, } -pub fn get_user_amount_and_new_status_and_penalized<'ix>( +pub fn get_user_amount_and_new_status_and_penalized( auction: &Auction, custody_token: &TokenAccount, auction_config: &AuctionConfig, init_auction_fee: u64, - active_auction_account_infos: ActiveAuctionAccountInfos<'ix>, + active_auction_account_infos: ActiveAuctionAccountInfos<'_>, accounts: &[AccountInfo], ) -> Result<(u64, AuctionStatus, bool)> { let auction_info = auction.info.as_ref().unwrap(); @@ -288,9 +288,9 @@ pub fn get_user_amount_and_new_status_and_penalized<'ix>( )) } -pub fn get_order_executed_event<'ix>( +pub fn get_order_executed_event( auction: &Auction, - fast_vaa: &AccountInfo<'ix>, + fast_vaa: &AccountInfo<'_>, auction_info: &AuctionInfo, penalized: bool, ) -> OrderExecuted { diff --git a/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs b/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs index 3bc3cae5b..0a4ba6bf8 100644 --- a/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/offer/place_initial/cctp.rs @@ -1,7 +1,9 @@ use crate::{ composite::*, error::MatchingEngineError, - state::{Auction, AuctionConfig, AuctionInfo, AuctionStatus, MessageProtocol}, + state::{ + Auction, AuctionConfig, AuctionInfo, AuctionParameters, AuctionStatus, MessageProtocol, + }, utils, }; use anchor_lang::prelude::*; @@ -131,12 +133,7 @@ pub fn place_initial_offer_cctp( // Saturating to u64::MAX is safe here. If the amount really ends up being this large, the // checked addition below will catch it. let security_deposit = - order - .max_fee() - .saturating_add(utils::auction::compute_notional_security_deposit( - &ctx.accounts.auction_config, - amount_in, - )); + calculate_security_deposit(order.max_fee(), amount_in, &ctx.accounts.auction_config); // Set up the Auction account for this auction. let config = &ctx.accounts.auction_config; @@ -206,3 +203,14 @@ pub fn place_initial_offer_cctp( .ok_or_else(|| MatchingEngineError::U64Overflow)?, ) } + +pub fn calculate_security_deposit( + max_fee: u64, + amount_in: u64, + auction_parameters: &AuctionParameters, +) -> u64 { + max_fee.saturating_add(utils::auction::compute_notional_security_deposit( + auction_parameters, + amount_in, + )) +} From 159d7ce1646304f5bf1a5dddf043ae820e4f79bd Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Fri, 16 May 2025 11:17:53 -0500 Subject: [PATCH 3/3] solana: refactor internal accounting --- .../src/fallback/processor/execute_order.rs | 33 +- .../auction/execute_fast_order/mod.rs | 373 +++++++++--------- 2 files changed, 198 insertions(+), 208 deletions(-) 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 4696e7b7b..1c06ab408 100644 --- a/solana/programs/matching-engine/src/fallback/processor/execute_order.rs +++ b/solana/programs/matching-engine/src/fallback/processor/execute_order.rs @@ -9,7 +9,7 @@ use solana_program::{instruction::Instruction, program::invoke_signed_unchecked} use crate::{ error::MatchingEngineError, - processor::{get_user_amount_and_new_status_and_penalized, ActiveAuctionAccountInfos}, + processor::ExecuteOrderInternalAccounting, state::{Auction, AuctionStatus, Custodian, MessageProtocol}, ID, }; @@ -347,24 +347,21 @@ pub(super) fn process(accounts: &[AccountInfo]) -> Result<()> { // in? // Prepare the execute order (get the user amount, fill, and order executed event) - let active_auction_accounts = ActiveAuctionAccountInfos { - auction_best_offer_token: auction_best_offer_token_info.to_account_info(), - auction_executor_token: executor_token_info.to_account_info(), - auction_initial_offer_token: auction_initial_offer_token_info.to_account_info(), - auction_custody_token: auction_custody_info.to_account_info(), - active_auction: active_auction_info.to_account_info(), - auction_custodian: custodian_info.to_account_info(), - }; - let custody_token = + let auction_custody = TokenAccount::try_deserialize(&mut &auction_custody_info.data.borrow()[..])?; - let (user_amount, new_status, _penalized) = get_user_amount_and_new_status_and_penalized( - &active_auction, - &custody_token, - &auction_config, - fast_market_order.init_auction_fee, - active_auction_accounts, - accounts, - )?; + + let (user_amount, new_status, _penalized) = ExecuteOrderInternalAccounting { + active_auction_key: active_auction_info.key, + active_auction: &active_auction, + auction_custody_key: auction_custody_info.key, + auction_custody: &auction_custody, + best_offer_token_info: auction_best_offer_token_info, + executor_token_key: executor_token_info.key, + initial_offer_token_info: auction_initial_offer_token_info, + custodian_key: custodian_info.key, + auction_config: &auction_config, + } + .into_calculate_and_transfer(fast_market_order.init_auction_fee, accounts)?; // Set the active auction status active_auction.status = new_status; diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs index fa7b58114..0deb4dc87 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs @@ -43,36 +43,29 @@ fn handle_execute_fast_order<'info>( .unwrap() .to_fast_market_order_unchecked(); - let active_auction_account_infos = ActiveAuctionAccountInfos { - auction_best_offer_token: execute_order - .active_auction - .best_offer_token - .to_account_info(), - auction_executor_token: execute_order.executor_token.to_account_info(), - auction_initial_offer_token: execute_order.initial_offer_token.to_account_info(), - auction_custody_token: execute_order.active_auction.custody_token.to_account_info(), - active_auction: auction.to_account_info(), - auction_custodian: custodian.to_account_info(), - }; - - let accounts = &[ - auction.to_account_info(), - custody_token.to_account_info(), - config.to_account_info(), - executor_token.to_account_info(), - best_offer_token.to_account_info(), - initial_offer_token.to_account_info(), - fast_vaa.to_account_info(), - token_program.to_account_info(), - ]; - - let (user_amount, new_status, penalized) = get_user_amount_and_new_status_and_penalized( - auction, - custody_token, - config, + let (user_amount, new_status, penalized) = ExecuteOrderInternalAccounting { + active_auction_key: &auction.key(), + active_auction: auction, + auction_custody_key: &custody_token.key(), + auction_custody: custody_token, + best_offer_token_info: &execute_order.active_auction.best_offer_token, + executor_token_key: &execute_order.executor_token.key(), + initial_offer_token_info: &execute_order.initial_offer_token, + custodian_key: &custodian.key(), + auction_config: config, + } + .into_calculate_and_transfer( order.init_auction_fee(), - active_auction_account_infos, - accounts, + &[ + auction.to_account_info(), + custody_token.to_account_info(), + config.to_account_info(), + executor_token.to_account_info(), + best_offer_token.to_account_info(), + initial_offer_token.to_account_info(), + fast_vaa.to_account_info(), + token_program.to_account_info(), + ], )?; let order_executed_event = @@ -96,196 +89,196 @@ fn handle_execute_fast_order<'info>( }) } -pub struct ActiveAuctionAccountInfos<'ix> { - pub auction_best_offer_token: AccountInfo<'ix>, - pub auction_executor_token: AccountInfo<'ix>, - pub auction_initial_offer_token: AccountInfo<'ix>, - pub auction_custody_token: AccountInfo<'ix>, - pub active_auction: AccountInfo<'ix>, - pub auction_custodian: AccountInfo<'ix>, +pub struct ExecuteOrderInternalAccounting<'ix, 'info> { + pub active_auction_key: &'ix Pubkey, + pub active_auction: &'ix Auction, + pub auction_custody_key: &'ix Pubkey, + pub auction_custody: &'ix TokenAccount, + pub best_offer_token_info: &'ix AccountInfo<'info>, + pub executor_token_key: &'ix Pubkey, + pub initial_offer_token_info: &'ix AccountInfo<'info>, + pub custodian_key: &'ix Pubkey, + pub auction_config: &'ix AuctionConfig, } -pub fn get_user_amount_and_new_status_and_penalized( - auction: &Auction, - custody_token: &TokenAccount, - auction_config: &AuctionConfig, - init_auction_fee: u64, - active_auction_account_infos: ActiveAuctionAccountInfos<'_>, - accounts: &[AccountInfo], -) -> Result<(u64, AuctionStatus, bool)> { - let auction_info = auction.info.as_ref().unwrap(); - let current_slot = Clock::get().unwrap().slot; +impl<'ix, 'info> ExecuteOrderInternalAccounting<'ix, 'info> { + pub fn into_calculate_and_transfer( + self, + init_auction_fee: u64, + accounts: &[AccountInfo], + ) -> Result<(u64, AuctionStatus, bool)> { + let Self { + active_auction_key, + active_auction: auction, + auction_custody_key, + auction_custody: custody_token, + best_offer_token_info, + executor_token_key, + initial_offer_token_info, + custodian_key, + auction_config, + } = self; - let ActiveAuctionAccountInfos { - auction_best_offer_token: active_auction_best_offer_token, - auction_executor_token: active_auction_executor_token, - auction_initial_offer_token: active_auction_initial_offer_token, - auction_custody_token: active_auction_custody_token, - active_auction, - auction_custodian: active_auction_custodian, - } = active_auction_account_infos; + let auction_info = auction.info.as_ref().unwrap(); + let current_slot = Clock::get().unwrap().slot; - let additional_grace_period = match auction.target_protocol { - MessageProtocol::Local { .. } => { - crate::EXECUTE_FAST_ORDER_LOCAL_ADDITIONAL_GRACE_PERIOD.into() - } - _ => None, - }; + let additional_grace_period = match auction.target_protocol { + MessageProtocol::Local { .. } => { + crate::EXECUTE_FAST_ORDER_LOCAL_ADDITIONAL_GRACE_PERIOD.into() + } + _ => None, + }; - let DepositPenalty { - penalty, - user_reward, - } = utils::auction::compute_deposit_penalty( - &auction_config.parameters, - auction_info, - current_slot, - additional_grace_period, - ); + let DepositPenalty { + penalty, + user_reward, + } = utils::auction::compute_deposit_penalty( + &auction_config.parameters, + auction_info, + current_slot, + additional_grace_period, + ); - let user_amount = auction_info - .amount_in - .saturating_sub(auction_info.offer_price) - .saturating_sub(init_auction_fee) - .saturating_add(user_reward); - let mut remaining_custodied_amount = custody_token.amount.saturating_sub(user_amount); + let user_amount = auction_info + .amount_in + .saturating_sub(auction_info.offer_price) + .saturating_sub(init_auction_fee) + .saturating_add(user_reward); + let mut remaining_custodied_amount = custody_token.amount.saturating_sub(user_amount); - let mut deposit_and_fee = auction_info - .offer_price - .saturating_add(auction_info.security_deposit) - .saturating_sub(user_reward); + let mut deposit_and_fee = auction_info + .offer_price + .saturating_add(auction_info.security_deposit) + .saturating_sub(user_reward); - let auction_signer_seeds = &[ - Auction::SEED_PREFIX, - auction.vaa_hash.as_ref(), - &[auction.bump], - ]; + let auction_signer_seeds = &[ + Auction::SEED_PREFIX, + auction.vaa_hash.as_ref(), + &[auction.bump], + ]; - let penalized = penalty > 0; + let penalized = penalty > 0; - if penalized && active_auction_best_offer_token.key() != active_auction_executor_token.key() { - deposit_and_fee = deposit_and_fee.saturating_sub(penalty); - } + if penalized && best_offer_token_info.key != executor_token_key { + deposit_and_fee = deposit_and_fee.saturating_sub(penalty); + } - if utils::checked_deserialize_token_account( - &active_auction_initial_offer_token, - &common::USDC_MINT, - ) - .is_some() - { - if active_auction_best_offer_token.key() != active_auction_initial_offer_token.key() { - // Pay the auction initiator their fee. - let transfer_ix = spl_token::instruction::transfer( - &spl_token::ID, - &active_auction_custody_token.key(), - &active_auction_initial_offer_token.key(), - &active_auction.key(), - &[], - init_auction_fee, - ) - .unwrap(); + if utils::checked_deserialize_token_account(initial_offer_token_info, &common::USDC_MINT) + .is_some() + { + if best_offer_token_info.key != initial_offer_token_info.key { + // Pay the auction initiator their fee. + let transfer_ix = spl_token::instruction::transfer( + &spl_token::ID, + auction_custody_key, + initial_offer_token_info.key, + active_auction_key, + &[], + init_auction_fee, + ) + .unwrap(); - 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 = - remaining_custodied_amount.saturating_sub(init_auction_fee); - } else { - // Add it to the reimbursement. - deposit_and_fee = deposit_and_fee - .checked_add(init_auction_fee) - .ok_or_else(|| MatchingEngineError::U64Overflow)?; + 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 = + remaining_custodied_amount.saturating_sub(init_auction_fee); + } else { + // Add it to the reimbursement. + deposit_and_fee = deposit_and_fee + .checked_add(init_auction_fee) + .ok_or_else(|| MatchingEngineError::U64Overflow)?; + } } - } - // Return the security deposit and the fee to the highest bidder. - if active_auction_best_offer_token.key() == active_auction_executor_token.key() { - // If the best offer token is equal to the executor token, just send whatever remains in - // the custody token account. - // - // NOTE: This will revert if the best offer token does not exist. But this will present - // an opportunity for another executor to execute this order and take what the best - // offer token would have received. - let transfer_ix = spl_token::instruction::transfer( - &spl_token::ID, - &active_auction_custody_token.key(), - &active_auction_best_offer_token.key(), - &active_auction.key(), - &[], - deposit_and_fee, - ) - .unwrap(); - msg!( - "Sending deposit and fee amount {} to best offer token account", - deposit_and_fee - ); - invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; - } else { - // Otherwise, send the deposit and fee to the best offer token. If the best offer token - // doesn't exist at this point (which would be unusual), we will reserve these funds - // for the executor token. - if utils::checked_deserialize_token_account( - &active_auction_best_offer_token, - &common::USDC_MINT, - ) - .is_some() - { + // Return the security deposit and the fee to the highest bidder. + if best_offer_token_info.key == executor_token_key { + // If the best offer token is equal to the executor token, just send whatever remains in + // the custody token account. + // + // NOTE: This will revert if the best offer token does not exist. But this will present + // an opportunity for another executor to execute this order and take what the best + // offer token would have received. let transfer_ix = spl_token::instruction::transfer( &spl_token::ID, - &active_auction_custody_token.key(), - &active_auction_best_offer_token.key(), - &active_auction.key(), + auction_custody_key, + best_offer_token_info.key, + active_auction_key, &[], deposit_and_fee, ) .unwrap(); msg!( - "Sending deposit and fee {} to best offer token account", + "Sending deposit and fee amount {} to best offer token account", deposit_and_fee ); invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; - remaining_custodied_amount = remaining_custodied_amount.saturating_sub(deposit_and_fee); - } + } else { + // Otherwise, send the deposit and fee to the best offer token. If the best offer token + // doesn't exist at this point (which would be unusual), we will reserve these funds + // for the executor token. + if utils::checked_deserialize_token_account(best_offer_token_info, &common::USDC_MINT) + .is_some() + { + let transfer_ix = spl_token::instruction::transfer( + &spl_token::ID, + auction_custody_key, + best_offer_token_info.key, + active_auction_key, + &[], + deposit_and_fee, + ) + .unwrap(); + msg!( + "Sending deposit and fee {} to best offer token account", + deposit_and_fee + ); + invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?; + remaining_custodied_amount = + remaining_custodied_amount.saturating_sub(deposit_and_fee); + } - // And pay the executor whatever remains in the auction custody token account. - if remaining_custodied_amount > 0 { - let instruction = spl_token::instruction::transfer( - &spl_token::ID, - &active_auction_custody_token.key(), - &active_auction_executor_token.key(), - &active_auction.key(), - &[], - remaining_custodied_amount, - ) - .unwrap(); - msg!( - "Sending remaining custodied amount {} to executor token account", - remaining_custodied_amount - ); - invoke_signed_unchecked(&instruction, accounts, &[auction_signer_seeds])?; + // And pay the executor whatever remains in the auction custody token account. + if remaining_custodied_amount > 0 { + let instruction = spl_token::instruction::transfer( + &spl_token::ID, + auction_custody_key, + executor_token_key, + active_auction_key, + &[], + remaining_custodied_amount, + ) + .unwrap(); + msg!( + "Sending remaining custodied amount {} to executor token account", + remaining_custodied_amount + ); + invoke_signed_unchecked(&instruction, accounts, &[auction_signer_seeds])?; + } } - } - // Set the authority of the custody token account to the custodian. He will take over from - // here. - let set_authority_ix = spl_token::instruction::set_authority( - &spl_token::ID, - &active_auction_custody_token.key(), - Some(&active_auction_custodian.key()), - spl_token::instruction::AuthorityType::AccountOwner, - &active_auction.key(), - &[], - ) - .unwrap(); + // Set the authority of the custody token account to the custodian. He will take over from + // here. + let set_authority_ix = spl_token::instruction::set_authority( + &spl_token::ID, + auction_custody_key, + Some(custodian_key), + spl_token::instruction::AuthorityType::AccountOwner, + active_auction_key, + &[], + ) + .unwrap(); - invoke_signed_unchecked(&set_authority_ix, accounts, &[auction_signer_seeds])?; + invoke_signed_unchecked(&set_authority_ix, accounts, &[auction_signer_seeds])?; - Ok(( - user_amount, - AuctionStatus::Completed { - slot: current_slot, - execute_penalty: if penalized { penalty.into() } else { None }, - }, - penalized, - )) + Ok(( + user_amount, + AuctionStatus::Completed { + slot: current_slot, + execute_penalty: if penalized { penalty.into() } else { None }, + }, + penalized, + )) + } } pub fn get_order_executed_event(