Skip to content

Commit

Permalink
solana: add base fee token
Browse files Browse the repository at this point in the history
  • Loading branch information
a5-pickle committed Aug 16, 2024
1 parent c6bd633 commit c9ac52c
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 158 deletions.
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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ use crate::{
utils,
};
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::get_associated_token_address,
token::{self, TokenAccount},
};
use anchor_spl::token::{self, TokenAccount};

#[derive(Accounts)]
pub struct SettleAuctionComplete<'info> {
Expand All @@ -18,20 +15,24 @@ pub struct SettleAuctionComplete<'info> {
mut,
address = prepared_order_response.prepared_by,
)]
executor: UncheckedAccount<'info>,
beneficiary: UncheckedAccount<'info>,

/// This token account will receive the base fee only if there was a penalty when executing the
/// order. If it does not exist when there is a penalty, this instruction handler will revert.
///
/// CHECK: This account must be the same as the base fee token in the prepared order response.
#[account(
mut,
token::mint = common::USDC_MINT,
address = prepared_order_response.base_fee_token,
)]
executor_token: Box<Account<'info, TokenAccount>>,
base_fee_token: UncheckedAccount<'info>,

/// Destination token account, which the redeemer may not own. But because the redeemer is a
/// signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent
/// to any account he chooses (this one).
///
/// CHECK: This token account may exist. If it doesn't and there is a penalty, we will send all
/// of the tokens to the executor token account.
/// of the tokens to the base fee token account.
#[account(
mut,
address = auction.info.as_ref().unwrap().best_offer_token,
Expand All @@ -40,7 +41,7 @@ pub struct SettleAuctionComplete<'info> {

#[account(
mut,
close = executor,
close = beneficiary,
seeds = [
PreparedOrderResponse::SEED_PREFIX,
prepared_order_response.seeds.fast_vaa_hash.as_ref()
Expand Down Expand Up @@ -101,8 +102,8 @@ fn handle_settle_auction_complete(
&[prepared_order_response.seeds.bump],
];

let executor = &ctx.accounts.executor;
let executor_token = &ctx.accounts.executor_token;
let beneficiary = &ctx.accounts.beneficiary;
let base_fee_token = &ctx.accounts.base_fee_token;
let best_offer_token = &ctx.accounts.best_offer_token;
let token_program = &ctx.accounts.token_program;
let prepared_custody_token = &ctx.accounts.prepared_custody_token;
Expand Down Expand Up @@ -131,11 +132,15 @@ fn handle_settle_auction_complete(
)
}
_ => {
let base_fee_token_data =
utils::checked_deserialize_token_account(base_fee_token, &common::USDC_MINT)
.ok_or_else(|| MatchingEngineError::BaseFeeTokenRequired)?;

// If the token account happens to not exist anymore, we will give everything to the
// executor.
// base fee token account.
match utils::checked_deserialize_token_account(best_offer_token, &common::USDC_MINT) {
Some(best_offer) => {
if executor_token.key() == best_offer_token.key() {
if base_fee_token.key() == best_offer_token.key() {
(
None, // executor_result
TokenAccountResult {
Expand All @@ -145,31 +150,9 @@ fn handle_settle_auction_complete(
.into(),
)
} else {
// Because the auction participant was penalized for executing the order
// late, he will be deducted the base fee. This base fee will be sent to the
// executor token account if it is not the same as the best offer token
// account.

// We require that the executor token account be an ATA.
require_keys_eq!(
executor_token.key(),
get_associated_token_address(
&executor_token.owner,
&executor_token.mint
),
ErrorCode::AccountNotAssociatedTokenAccount
);

// And enforce that the owner of this ATA is the executor.
require_keys_eq!(
executor.key(),
executor_token.owner,
ErrorCode::ConstraintTokenOwner,
);

(
TokenAccountResult {
balance_before: executor_token.amount,
balance_before: base_fee_token_data.amount,
amount: base_fee,
}
.into(),
Expand All @@ -183,7 +166,7 @@ fn handle_settle_auction_complete(
}
None => (
TokenAccountResult {
balance_before: executor_token.amount,
balance_before: base_fee_token_data.amount,
amount: repayment,
}
.into(),
Expand All @@ -193,7 +176,7 @@ fn handle_settle_auction_complete(
}
};

// Transfer executor his bounty if there are any.
// Transfer base fee token his bounty if there are any.
let settled_executor_result = match executor_result {
Some(TokenAccountResult {
balance_before,
Expand All @@ -204,7 +187,7 @@ fn handle_settle_auction_complete(
token_program.to_account_info(),
token::Transfer {
from: prepared_custody_token.to_account_info(),
to: executor_token.to_account_info(),
to: base_fee_token.to_account_info(),
authority: prepared_order_response.to_account_info(),
},
&[prepared_order_response_signer_seeds],
Expand All @@ -213,7 +196,7 @@ fn handle_settle_auction_complete(
)?;

SettledTokenAccountInfo {
key: executor_token.key(),
key: base_fee_token.key(),
balance_after: balance_before.saturating_add(amount),
}
.into()
Expand Down Expand Up @@ -252,7 +235,7 @@ fn handle_settle_auction_complete(
emit!(crate::events::AuctionSettled {
auction: ctx.accounts.auction.key(),
best_offer_token: settled_best_offer_result,
executor_token: settled_executor_result,
base_fee_token: settled_executor_result,
with_execute: Default::default(),
});

Expand All @@ -261,7 +244,7 @@ fn handle_settle_auction_complete(
token_program.to_account_info(),
token::CloseAccount {
account: prepared_custody_token.to_account_info(),
destination: executor.to_account_info(),
destination: beneficiary.to_account_info(),
authority: prepared_order_response.to_account_info(),
},
&[prepared_order_response_signer_seeds],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> R
emit!(crate::events::AuctionSettled {
auction: auction.key(),
best_offer_token: Default::default(),
executor_token: crate::events::SettledTokenAccountInfo {
base_fee_token: crate::events::SettledTokenAccountInfo {
key: fee_recipient_token.key(),
balance_after: fee_recipient_token.amount.saturating_add(fee)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct PreparedOrderResponseSeeds {
#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace)]
pub struct PreparedOrderResponseInfo {
pub prepared_by: Pubkey,
pub base_fee_token: Pubkey,

pub fast_vaa_timestamp: u32,
pub source_chain: u16,
Expand Down
3 changes: 2 additions & 1 deletion solana/programs/matching-engine/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn require_local_endpoint(endpoint: &RouterEndpoint) -> Result<bool> {
pub fn checked_deserialize_token_account(
acc_info: &AccountInfo,
expected_mint: &Pubkey,
) -> Option<token::TokenAccount> {
) -> Option<Box<token::TokenAccount>> {
if acc_info.owner != &token::ID {
None
} else {
Expand All @@ -54,5 +54,6 @@ pub fn checked_deserialize_token_account(
token::TokenAccount::try_deserialize(&mut &data[..])
.ok()
.filter(|token_data| &token_data.mint == expected_mint && !token_data.is_frozen())
.map(Box::new)
}
}
37 changes: 32 additions & 5 deletions solana/ts/src/idl/json/matching_engine.json
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,15 @@
"name": "prepared_custody_token",
"writable": true
},
{
"name": "base_fee_token",
"docs": [
"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."
]
},
{
"name": "usdc",
"accounts": [
Expand Down Expand Up @@ -1834,14 +1843,19 @@
],
"accounts": [
{
"name": "executor",
"name": "beneficiary",
"docs": [
"finalized VAA."
],
"writable": true
},
{
"name": "executor_token",
"name": "base_fee_token",
"docs": [
"This token account will receive the base fee only if there was a penalty when executing the",
"order. If it does not exist when there is a penalty, this instruction handler will revert.",
""
],
"writable": true
},
{
Expand All @@ -1851,7 +1865,7 @@
"signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent",
"to any account he chooses (this one).",
"",
"of the tokens to the executor token account."
"of the tokens to the base fee token account."
],
"writable": true
},
Expand Down Expand Up @@ -3032,6 +3046,14 @@
"code": 7082,
"name": "AuctionAlreadySettled"
},
{
"code": 7084,
"name": "InvalidBaseFeeToken"
},
{
"code": 7086,
"name": "BaseFeeTokenRequired"
},
{
"code": 7280,
"name": "CannotCloseAuctionYet"
Expand Down Expand Up @@ -3474,10 +3496,11 @@
}
},
{
"name": "executor_token",
"name": "base_fee_token",
"docs": [
"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)."
"base fee token account (if there was an auction) or fee recipient token (if there was no",
"auction)."
],
"type": {
"option": {
Expand Down Expand Up @@ -4103,6 +4126,10 @@
"name": "prepared_by",
"type": "pubkey"
},
{
"name": "base_fee_token",
"type": "pubkey"
},
{
"name": "fast_vaa_timestamp",
"type": "u32"
Expand Down
Loading

0 comments on commit c9ac52c

Please sign in to comment.