Skip to content

Commit

Permalink
solana: init auction in reserve fast fill seq (#174)
Browse files Browse the repository at this point in the history
Co-authored-by: A5 Pickle <[email protected]>
  • Loading branch information
a5-pickle and a5-pickle authored Jun 18, 2024
1 parent b43bf25 commit d5f4a1d
Show file tree
Hide file tree
Showing 21 changed files with 261 additions and 191 deletions.
47 changes: 47 additions & 0 deletions solana/programs/matching-engine/src/composite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,53 @@ pub struct ReserveFastFillSequence<'info> {
)]
pub reserved: Box<Account<'info, ReservedFastFillSequence>>,

/// CHECK: This auction account may not exist. If it does not exist, the prepared order response
/// must have been created by this point. Otherwise the auction account must reflect a completed
/// auction.
#[account(
init_if_needed,
payer = payer,
space = if auction.data_is_empty() {
8 + Auction::INIT_SPACE_NO_AUCTION
} else {
auction.data_len()
},
seeds = [
Auction::SEED_PREFIX,
fast_order_path.fast_vaa.load_unchecked().digest().as_ref(),
],
bump,
constraint = match &auction.info {
Some(info) => {
// Verify that the auction is active.
require_eq!(
&auction.status,
&AuctionStatus::Active,
MatchingEngineError::AuctionNotActive
);
// Out of paranoia, check that the auction is for a local fill.
require!(
matches!(auction.target_protocol, MessageProtocol::Local { .. }),
MatchingEngineError::InvalidTargetRouter
);
true
},
None => {
// This check makes sure that the auction account did not exist before this
// instruction was called.
require!(
auction.vaa_hash == [0; 32],
MatchingEngineError::AuctionExists,
);
true
}
},
)]
pub auction: Account<'info, Auction>,

system_program: Program<'info, System>,
}

Expand Down
3 changes: 2 additions & 1 deletion solana/programs/matching-engine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub enum MatchingEngineError {
InvalidOfferToken = 0x424,
FastFillTooLarge = 0x426,
AuctionExists = 0x428,
AccountNotAuction = 0x429,
NoAuction = 0x429,
BestOfferTokenMismatch = 0x42a,
BestOfferTokenRequired = 0x42c,
PreparedByMismatch = 0x42e,
Expand All @@ -80,6 +80,7 @@ pub enum MatchingEngineError {
FastFillAlreadyRedeemed = 0x434,
FastFillNotRedeemed = 0x435,
ReservedSequenceMismatch = 0x438,
AuctionAlreadySettled = 0x43a,

CannotCloseAuctionYet = 0x500,
AuctionHistoryNotFull = 0x502,
Expand Down
9 changes: 9 additions & 0 deletions solana/programs/matching-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,15 @@ pub mod matching_engine {
/// This sequence number is warehoused in the `ReservedFastFillSequence` account and will be
/// closed when the funds are finally settled.
///
/// NOTE: This instruction is expected to be in the same transaction as the one that executes
/// the prepare order response instruction. If it is not, there is a risk that after preparing
/// the order response that someone starts an auction. This scenario risks the preparer's rent
/// that he paid because the winning auction participant can take his lamports when he calls
/// settle auction complete. Although this is an unlikely scenario, it is possible if there is
/// no deadline specified to start the auction and no participants use the fast VAA to start an
/// auction until the finalized VAA exists (which guarantees that the funds have finalized on
/// the source network).
///
/// # Arguments
///
/// * `ctx` - `ReserveFastFillSequenceNoAuction` context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub struct ExecuteFastOrderLocal<'info> {
#[account(
init,
payer = payer,
space = FastFill::checked_compute_size({
space = FastFill::compute_size({
let vaa = execute_order.fast_vaa.load_unchecked();
// We can unwrap and convert to FastMarketOrder unchecked because we validate the VAA
Expand All @@ -64,8 +64,7 @@ pub struct ExecuteFastOrderLocal<'info> {
.to_fast_market_order_unchecked();
order.redeemer_message_len().into()
})
.ok_or(MatchingEngineError::FastFillTooLarge)?,
}),
seeds = [
FastFill::SEED_PREFIX,
&reserved_sequence.fast_fill_seeds.source_chain.to_be_bytes(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ fn handle_settle_auction_none_cctp(
ctx: Context<SettleAuctionNoneCctp>,
destination_cctp_domain: u32,
) -> Result<()> {
let auction = &mut ctx.accounts.auction;

// First set data in the auction account.
auction.set_inner(
ctx.accounts
.prepared
.order_response
.new_auction_placeholder(ctx.bumps.auction),
);

let prepared_by = &ctx.accounts.prepared.by;
let prepared_custody_token = &ctx.accounts.prepared.custody_token;
let custodian = &ctx.accounts.custodian;
Expand All @@ -92,17 +102,14 @@ fn handle_settle_auction_none_cctp(
let super::SettledNone {
user_amount: amount,
fill,
} = super::settle_none_and_prepare_fill(
super::SettleNoneAndPrepareFill {
prepared_order_response: &mut ctx.accounts.prepared.order_response,
prepared_custody_token,
auction: &mut ctx.accounts.auction,
fee_recipient_token: &ctx.accounts.fee_recipient_token,
custodian,
token_program,
},
ctx.bumps.auction,
)?;
} = super::settle_none_and_prepare_fill(super::SettleNoneAndPrepareFill {
prepared_order_response: &mut ctx.accounts.prepared.order_response,
prepared_custody_token,
auction: &mut ctx.accounts.auction,
fee_recipient_token: &ctx.accounts.fee_recipient_token,
custodian,
token_program,
})?;

let EndpointInfo {
chain: _,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
composite::*,
error::MatchingEngineError,
state::{Auction, Custodian, FastFill, ReservedFastFillSequence},
state::{Auction, AuctionStatus, Custodian, FastFill, ReservedFastFillSequence},
};
use anchor_lang::prelude::*;
use anchor_spl::token;
Expand Down Expand Up @@ -39,16 +39,25 @@ pub struct SettleAuctionNoneLocal<'info> {
)]
prepared: ClosePreparedOrderResponse<'info>,

/// There should be no account data here because an auction was never created.
/// This account will have been created using the reserve fast fill sequence (no auction)
/// instruction. We need to make sure that this account has not been used in an auction.
#[account(
init,
payer = payer,
space = 8 + Auction::INIT_SPACE_NO_AUCTION,
mut,
seeds = [
Auction::SEED_PREFIX,
prepared.order_response.seeds.fast_vaa_hash.as_ref(),
],
bump,
constraint = {
// Block this instruction if the auction status already has a meaningful value.
require_eq!(
&auction.status,
&AuctionStatus::NotStarted,
MatchingEngineError::AuctionExists
);
true
}
)]
auction: Box<Account<'info, Auction>>,

Expand Down Expand Up @@ -81,7 +90,7 @@ pub struct SettleAuctionNoneLocal<'info> {
#[account(
init,
payer = payer,
space = FastFill::checked_compute_size(prepared.order_response.redeemer_message.len()).unwrap(),
space = FastFill::compute_size(prepared.order_response.redeemer_message.len()),
seeds = [
FastFill::SEED_PREFIX,
&reserved_sequence.fast_fill_seeds.source_chain.to_be_bytes(),
Expand Down Expand Up @@ -117,17 +126,14 @@ pub fn settle_auction_none_local(ctx: Context<SettleAuctionNoneLocal>) -> Result
let super::SettledNone {
user_amount: amount,
fill,
} = super::settle_none_and_prepare_fill(
super::SettleNoneAndPrepareFill {
prepared_order_response: &mut ctx.accounts.prepared.order_response,
prepared_custody_token,
auction: &mut ctx.accounts.auction,
fee_recipient_token: &ctx.accounts.fee_recipient_token,
custodian,
token_program,
},
ctx.bumps.auction,
)?;
} = super::settle_none_and_prepare_fill(super::SettleNoneAndPrepareFill {
prepared_order_response: &mut ctx.accounts.prepared.order_response,
prepared_custody_token,
auction: &mut ctx.accounts.auction,
fee_recipient_token: &ctx.accounts.fee_recipient_token,
custodian,
token_program,
})?;

let fast_fill = FastFill::new(
fill,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ struct SettledNone {
fill: Fill,
}

fn settle_none_and_prepare_fill(
accounts: SettleNoneAndPrepareFill<'_, '_>,
auction_bump_seed: u8,
) -> Result<SettledNone> {
fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> Result<SettledNone> {
let SettleNoneAndPrepareFill {
prepared_order_response,
prepared_custody_token,
Expand Down Expand Up @@ -78,20 +75,11 @@ fn settle_none_and_prepare_fill(
custodian.key().into(),
)?;

// This is a necessary security check. This will prevent a relayer from starting an auction with
// the fast transfer VAA, even though the slow relayer already delivered the slow VAA. Not
// setting this could lead to trapped funds (which would require an upgrade to fix).
auction.set_inner(Auction {
bump: auction_bump_seed,
vaa_hash: prepared_order_response.seeds.fast_vaa_hash,
vaa_timestamp: prepared_order_response.fast_vaa_timestamp,
target_protocol: prepared_order_response.to_endpoint.protocol,
status: AuctionStatus::Settled {
fee,
total_penalty: None,
},
info: None,
});
// Indicate that the auction has been settled.
auction.status = AuctionStatus::Settled {
fee,
total_penalty: None,
};

emit!(crate::events::AuctionSettled {
auction: auction.key(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,29 @@
use crate::{
composite::*,
error::MatchingEngineError,
state::{Auction, AuctionConfig, AuctionStatus, MessageProtocol},
};
use crate::{composite::*, error::MatchingEngineError, state::AuctionConfig};
use anchor_lang::prelude::*;
use anchor_spl::token;

#[derive(Accounts)]
pub struct ReserveFastFillSequenceActiveAuction<'info> {
reserve_sequence: ReserveFastFillSequence<'info>,

/// CHECK: This auction account may not exist. If it does not exist, the prepared order response
/// must have been created by this point. Otherwise the auction account must reflect a completed
/// auction.
#[account(
seeds = [
Auction::SEED_PREFIX,
reserve_sequence.fast_order_path.fast_vaa.load_unchecked().digest().as_ref(),
],
bump = auction.bump,
constraint = {
// Verify that the auction is active.
require_eq!(
&auction.status,
&AuctionStatus::Active,
MatchingEngineError::AuctionNotActive
);
// Out of paranoia, check that the auction is for a local fill.
require!(
matches!(auction.target_protocol, MessageProtocol::Local { .. }),
MatchingEngineError::InvalidTargetRouter
);
true
}
)]
auction: Account<'info, Auction>,

#[account(
constraint = {
// We know from the auction constraint that the auction is active, so the auction info
// is safe to unwrap.
let info = auction.info.as_ref().unwrap();
constraint = match &reserve_sequence.auction.info {
Some(info) => {
// Verify that the auction period has expired.
require_eq!(
info.config_id,
auction_config.id,
MatchingEngineError::AuctionConfigMismatch
);
require!(
!info.within_auction_duration(&auction_config),
MatchingEngineError::AuctionPeriodNotExpired
);
// Verify that the auction period has expired.
require_eq!(
info.config_id,
auction_config.id,
MatchingEngineError::AuctionConfigMismatch
);
require!(
!info.within_auction_duration(&auction_config),
MatchingEngineError::AuctionPeriodNotExpired
);
true
true
},
_ => return err!(MatchingEngineError::NoAuction),
}
)]
auction_config: Account<'info, AuctionConfig>,
Expand All @@ -70,7 +37,7 @@ pub struct ReserveFastFillSequenceActiveAuction<'info> {
constraint = {
// We know from the auction constraint that the auction is active, so the auction info
// is safe to unwrap.
let info = auction.info.as_ref().unwrap();
let info = reserve_sequence.auction.info.as_ref().unwrap();
// Best offer token must equal the one in the auction account.
//
Expand Down Expand Up @@ -110,10 +77,12 @@ pub fn reserve_fast_fill_sequence_active_auction(
require_keys_eq!(token.owner, beneficiary, ErrorCode::ConstraintTokenOwner);
}

let fast_vaa_hash = ctx.accounts.reserve_sequence.auction.vaa_hash;

super::set_reserved_sequence_data(
&mut ctx.accounts.reserve_sequence,
&ctx.bumps.reserve_sequence,
ctx.accounts.auction.vaa_hash,
fast_vaa_hash,
beneficiary,
)
}
Loading

0 comments on commit d5f4a1d

Please sign in to comment.