Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

solana: pay the original payers back #193

Merged
merged 10 commits into from
Sep 30, 2024
16 changes: 9 additions & 7 deletions solana/programs/matching-engine/src/composite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ pub struct ActiveAuction<'info> {
],
bump = auction.info.as_ref().unwrap().custody_token_bump,
)]
pub custody_token: Account<'info, anchor_spl::token::TokenAccount>,
pub custody_token: Box<Account<'info, token::TokenAccount>>,

#[account(
constraint = {
Expand All @@ -332,7 +332,7 @@ pub struct ActiveAuction<'info> {
true
},
)]
pub config: Account<'info, crate::state::AuctionConfig>,
pub config: Box<Account<'info, crate::state::AuctionConfig>>,

/// CHECK: Mutable. Must have the same key in auction data.
#[account(
Expand Down Expand Up @@ -398,9 +398,11 @@ pub struct ExecuteOrder<'info> {
)]
pub initial_offer_token: UncheckedAccount<'info>,

/// CHECK: Must be the owner of initial offer token account. If the initial offer token account
/// does not exist anymore, we will attempt to perform this check.
#[account(mut)]
/// CHECK: Must be the payer of the initial auction (see [Auction::prepared_by]).
#[account(
mut,
address = active_auction.prepared_by,
)]
pub initial_participant: UncheckedAccount<'info>,
}

Expand Down Expand Up @@ -563,7 +565,7 @@ impl<'info> VaaDigest for ClosePreparedOrderResponse<'info> {
#[derive(Accounts)]
pub struct ReserveFastFillSequence<'info> {
#[account(mut)]
payer: Signer<'info>,
pub payer: Signer<'info>,

pub fast_order_path: FastOrderPath<'info>,

Expand Down Expand Up @@ -650,7 +652,7 @@ pub struct ReserveFastFillSequence<'info> {
}
},
)]
pub auction: Account<'info, Auction>,
pub auction: Box<Account<'info, Auction>>,

system_program: Program<'info, System>,
}
Expand Down
2 changes: 2 additions & 0 deletions solana/programs/matching-engine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub enum MatchingEngineError {
FastFillNotRedeemed = 0x435,
ReservedSequenceMismatch = 0x438,
AuctionAlreadySettled = 0x43a,
InvalidBaseFeeToken = 0x43c,
BaseFeeTokenRequired = 0x43e,

CannotCloseAuctionYet = 0x500,
AuctionHistoryNotFull = 0x502,
Expand Down
5 changes: 3 additions & 2 deletions solana/programs/matching-engine/src/events/auction_settled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ pub struct AuctionSettled {
pub best_offer_token: Option<SettledTokenAccountInfo>,

/// Depending on whether there was an active auction, this field will have the pubkey of the
/// executor token (if there was an auction) or fee recipient token (if there was no auction).
pub executor_token: Option<SettledTokenAccountInfo>,
/// base fee token account (if there was an auction) or fee recipient token (if there was no
/// auction).
pub base_fee_token: Option<SettledTokenAccountInfo>,

/// This value will only be some if there was no active auction.
pub with_execute: Option<MessageProtocol>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,11 @@ pub fn handle_execute_fast_order_cctp(
let super::PreparedOrderExecution {
user_amount: amount,
fill,
beneficiary,
} = super::prepare_order_execution(super::PrepareFastExecution {
execute_order: &mut ctx.accounts.execute_order,
custodian: &ctx.accounts.custodian,
token_program: &ctx.accounts.token_program,
})?;
} = super::handle_execute_fast_order(
&mut ctx.accounts.execute_order,
&ctx.accounts.custodian,
&ctx.accounts.token_program,
)?;

let active_auction = &ctx.accounts.execute_order.active_auction;
let auction_custody_token = &active_auction.custody_token;
Expand Down Expand Up @@ -188,7 +187,11 @@ pub fn handle_execute_fast_order_cctp(
token_program.to_account_info(),
token::CloseAccount {
account: auction_custody_token.to_account_info(),
destination: beneficiary.unwrap_or_else(|| payer.to_account_info()),
destination: ctx
.accounts
.execute_order
.initial_participant
.to_account_info(),
authority: custodian.to_account_info(),
},
&[Custodian::SIGNER_SEEDS],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,11 @@ pub fn execute_fast_order_local(ctx: Context<ExecuteFastOrderLocal>) -> Result<(
let super::PreparedOrderExecution {
user_amount: amount,
fill,
beneficiary,
} = super::prepare_order_execution(super::PrepareFastExecution {
execute_order: &mut ctx.accounts.execute_order,
custodian,
token_program,
})?;
} = super::handle_execute_fast_order(
&mut ctx.accounts.execute_order,
&ctx.accounts.custodian,
&ctx.accounts.token_program,
)?;

let fast_fill = FastFill::new(
fill,
Expand Down Expand Up @@ -140,7 +139,11 @@ pub fn execute_fast_order_local(ctx: Context<ExecuteFastOrderLocal>) -> Result<(
token_program.to_account_info(),
token::CloseAccount {
account: auction_custody_token.to_account_info(),
destination: beneficiary.unwrap_or_else(|| ctx.accounts.payer.to_account_info()),
destination: ctx
.accounts
.execute_order
.initial_participant
.to_account_info(),
authority: custodian.to_account_info(),
},
&[Custodian::SIGNER_SEEDS],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,30 @@ use common::messages::{
Fill,
};

struct PrepareFastExecution<'ctx, 'info> {
execute_order: &'ctx mut ExecuteOrder<'info>,
custodian: &'ctx CheckedCustodian<'info>,
token_program: &'ctx Program<'info, token::Token>,
}

struct PreparedOrderExecution<'info> {
struct PreparedOrderExecution {
pub user_amount: u64,
pub fill: Fill,
pub beneficiary: Option<AccountInfo<'info>>,
}

fn prepare_order_execution<'info>(
accounts: PrepareFastExecution<'_, 'info>,
) -> Result<PreparedOrderExecution<'info>> {
let PrepareFastExecution {
execute_order,
custodian,
token_program,
} = accounts;

fn handle_execute_fast_order<'info>(
execute_order: &mut ExecuteOrder<'info>,
custodian: &CheckedCustodian<'info>,
token_program: &Program<'info, token::Token>,
) -> Result<PreparedOrderExecution> {
let auction = &mut execute_order.active_auction.auction;
let fast_vaa = &execute_order.fast_vaa;
let custody_token = &execute_order.active_auction.custody_token;
let config = &execute_order.active_auction.config;
let executor_token = &execute_order.executor_token;
let best_offer_token = &execute_order.active_auction.best_offer_token;
let initial_offer_token = &execute_order.initial_offer_token;
let initial_participant = &execute_order.initial_participant;

let vaa = fast_vaa.load_unchecked();
let order = LiquidityLayerMessage::try_from(vaa.payload())
.unwrap()
.to_fast_market_order_unchecked();

let (user_amount, new_status, beneficiary) = {
let (user_amount, new_status) = {
let auction_info = auction.info.as_ref().unwrap();
let current_slot = Clock::get().unwrap().slot;

Expand Down Expand Up @@ -107,32 +95,19 @@ fn prepare_order_execution<'info>(
deposit_and_fee = deposit_and_fee.saturating_sub(penalty);
}

let mut beneficiary = None;

// 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.
//
// Deserialize to token account to find owner. We check that this is a legitimate token
// account.
if let Some(token_data) =
utils::checked_deserialize_token_account(initial_offer_token, &custody_token.mint)
// We check that this is a legitimate token account.
if utils::checked_deserialize_token_account(initial_offer_token, &common::USDC_MINT)
.is_some()
{
// Before setting the beneficiary to the initial participant, we need to make sure that
// he is the owner of this token account.
require_keys_eq!(
token_data.owner,
initial_participant.key(),
ErrorCode::ConstraintTokenOwner
);

beneficiary.replace(initial_participant.to_account_info());

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(),
anchor_spl::token::Transfer {
token::Transfer {
from: custody_token.to_account_info(),
to: initial_offer_token.to_account_info(),
authority: auction.to_account_info(),
Expand Down Expand Up @@ -165,7 +140,7 @@ fn prepare_order_execution<'info>(
token::transfer(
CpiContext::new_with_signer(
token_program.to_account_info(),
anchor_spl::token::Transfer {
token::Transfer {
from: custody_token.to_account_info(),
to: best_offer_token.to_account_info(),
authority: auction.to_account_info(),
Expand All @@ -178,13 +153,13 @@ fn prepare_order_execution<'info>(
// 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, &custody_token.mint)
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(),
anchor_spl::token::Transfer {
token::Transfer {
from: custody_token.to_account_info(),
to: best_offer_token.to_account_info(),
authority: auction.to_account_info(),
Expand All @@ -203,7 +178,7 @@ fn prepare_order_execution<'info>(
token::transfer(
CpiContext::new_with_signer(
token_program.to_account_info(),
anchor_spl::token::Transfer {
token::Transfer {
from: custody_token.to_account_info(),
to: executor_token.to_account_info(),
authority: auction.to_account_info(),
Expand Down Expand Up @@ -246,7 +221,6 @@ fn prepare_order_execution<'info>(
slot: current_slot,
execute_penalty: if penalized { penalty.into() } else { None },
},
beneficiary,
)
};

Expand All @@ -264,6 +238,5 @@ fn prepare_order_execution<'info>(
.try_into()
.map_err(|_| MatchingEngineError::RedeemerMessageTooLarge)?,
},
beneficiary,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use crate::{
},
};
use anchor_lang::{prelude::*, system_program};
use anchor_spl::token;

#[derive(Accounts)]
pub struct AddAuctionHistoryEntry<'info> {
Expand Down Expand Up @@ -62,21 +61,13 @@ pub struct AddAuctionHistoryEntry<'info> {
)]
auction: Account<'info, Auction>,

/// CHECK: This account will either be the owner of the fee recipient token account (if there
/// was no auction) or the owner of the initial offer token account.
#[account(mut)]
beneficiary: UncheckedAccount<'info>,

/// CHECK: This account is whoever originally created the auction account (see
/// [Auction::prepared_by].
#[account(
token::authority = beneficiary,
address = {
match &auction.info {
Some(info) => info.initial_offer_token,
None => custodian.fee_recipient_token,
}
}
mut,
address = auction.prepared_by,
)]
beneficiary_token: Account<'info, token::TokenAccount>,
beneficiary: UncheckedAccount<'info>,

system_program: Program<'info, system_program::System>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn improve_offer(ctx: Context<ImproveOffer>, offer_price: u64) -> Result<()>
// If the best offer token happens to be closed, we will just keep the funds in the
// auction custody account. The executor token account will collect these funds when the
// order is executed.
if utils::checked_deserialize_token_account(best_offer_token, &custody_token.mint)
if utils::checked_deserialize_token_account(best_offer_token, &common::USDC_MINT)
.is_some()
{
token::transfer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ pub fn place_initial_offer_cctp(
vaa_timestamp: fast_vaa.timestamp(),
target_protocol: ctx.accounts.fast_order_path.to_endpoint.protocol,
status: AuctionStatus::Active,
prepared_by: ctx.accounts.payer.key(),
info: AuctionInfo {
config_id: config.id,
custody_token_bump: ctx.bumps.auction_custody_token,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ pub struct PrepareOrderResponseCctp<'info> {
)]
prepared_custody_token: Box<Account<'info, token::TokenAccount>>,

/// This token account will be the one that collects the base fee only if an auction's order
/// was executed late. Otherwise, the protocol's fee recipient token account will be used for
/// non-existent auctions and the best offer token account will be used for orders executed on
/// time.
#[account(
token::mint = usdc,
constraint = {
require!(
base_fee_token.key() != prepared_custody_token.key(),
MatchingEngineError::InvalidBaseFeeToken
);

true
}
)]
base_fee_token: Box<Account<'info, token::TokenAccount>>,

usdc: Usdc<'info>,

cctp: CctpReceiveMessage<'info>,
Expand Down Expand Up @@ -216,6 +233,7 @@ fn handle_prepare_order_response_cctp(
},
info: PreparedOrderResponseInfo {
prepared_by: ctx.accounts.payer.key(),
base_fee_token: ctx.accounts.base_fee_token.key(),
source_chain: finalized_vaa.emitter_chain(),
base_fee: order_response.base_fee(),
fast_vaa_timestamp: fast_vaa.timestamp(),
Expand Down
Loading
Loading