Skip to content

Commit 75ebbfb

Browse files
committed
solana: add base fee token
1 parent 04493eb commit 75ebbfb

File tree

13 files changed

+215
-158
lines changed

13 files changed

+215
-158
lines changed

solana/programs/matching-engine/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub enum MatchingEngineError {
8181
FastFillNotRedeemed = 0x435,
8282
ReservedSequenceMismatch = 0x438,
8383
AuctionAlreadySettled = 0x43a,
84+
InvalidBaseFeeToken = 0x43c,
85+
BaseFeeTokenRequired = 0x43e,
8486

8587
CannotCloseAuctionYet = 0x500,
8688
AuctionHistoryNotFull = 0x502,

solana/programs/matching-engine/src/events/auction_settled.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ pub struct AuctionSettled {
1818
pub best_offer_token: Option<SettledTokenAccountInfo>,
1919

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

2425
/// This value will only be some if there was no active auction.
2526
pub with_execute: Option<MessageProtocol>,

solana/programs/matching-engine/src/processor/auction/prepare_order_response/cctp.rs

+18
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ pub struct PrepareOrderResponseCctp<'info> {
9797
)]
9898
prepared_custody_token: Box<Account<'info, token::TokenAccount>>,
9999

100+
/// This token account will be the one that collects the base fee only if an auction's order
101+
/// was executed late. Otherwise, the protocol's fee recipient token account will be used for
102+
/// non-existent auctions and the best offer token account will be used for orders executed on
103+
/// time.
104+
#[account(
105+
token::mint = usdc,
106+
constraint = {
107+
require!(
108+
base_fee_token.key() != prepared_custody_token.key(),
109+
MatchingEngineError::InvalidBaseFeeToken
110+
);
111+
112+
true
113+
}
114+
)]
115+
base_fee_token: Box<Account<'info, token::TokenAccount>>,
116+
100117
usdc: Usdc<'info>,
101118

102119
cctp: CctpReceiveMessage<'info>,
@@ -216,6 +233,7 @@ fn handle_prepare_order_response_cctp(
216233
},
217234
info: PreparedOrderResponseInfo {
218235
prepared_by: ctx.accounts.payer.key(),
236+
base_fee_token: ctx.accounts.base_fee_token.key(),
219237
source_chain: finalized_vaa.emitter_chain(),
220238
base_fee: order_response.base_fee(),
221239
fast_vaa_timestamp: fast_vaa.timestamp(),

solana/programs/matching-engine/src/processor/auction/settle/complete.rs

+25-42
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ use crate::{
55
utils,
66
};
77
use anchor_lang::prelude::*;
8-
use anchor_spl::{
9-
associated_token::get_associated_token_address,
10-
token::{self, TokenAccount},
11-
};
8+
use anchor_spl::token::{self, TokenAccount};
129

1310
#[derive(Accounts)]
1411
pub struct SettleAuctionComplete<'info> {
@@ -18,20 +15,24 @@ pub struct SettleAuctionComplete<'info> {
1815
mut,
1916
address = prepared_order_response.prepared_by,
2017
)]
21-
executor: UncheckedAccount<'info>,
18+
beneficiary: UncheckedAccount<'info>,
2219

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

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

4142
#[account(
4243
mut,
43-
close = executor,
44+
close = beneficiary,
4445
seeds = [
4546
PreparedOrderResponse::SEED_PREFIX,
4647
prepared_order_response.seeds.fast_vaa_hash.as_ref()
@@ -101,8 +102,8 @@ fn handle_settle_auction_complete(
101102
&[prepared_order_response.seeds.bump],
102103
];
103104

104-
let executor = &ctx.accounts.executor;
105-
let executor_token = &ctx.accounts.executor_token;
105+
let beneficiary = &ctx.accounts.beneficiary;
106+
let base_fee_token = &ctx.accounts.base_fee_token;
106107
let best_offer_token = &ctx.accounts.best_offer_token;
107108
let token_program = &ctx.accounts.token_program;
108109
let prepared_custody_token = &ctx.accounts.prepared_custody_token;
@@ -131,11 +132,15 @@ fn handle_settle_auction_complete(
131132
)
132133
}
133134
_ => {
135+
let base_fee_token_data =
136+
utils::checked_deserialize_token_account(base_fee_token, &common::USDC_MINT)
137+
.ok_or_else(|| MatchingEngineError::BaseFeeTokenRequired)?;
138+
134139
// If the token account happens to not exist anymore, we will give everything to the
135-
// executor.
140+
// base fee token account.
136141
match utils::checked_deserialize_token_account(best_offer_token, &common::USDC_MINT) {
137142
Some(best_offer) => {
138-
if executor_token.key() == best_offer_token.key() {
143+
if base_fee_token.key() == best_offer_token.key() {
139144
(
140145
None, // executor_result
141146
TokenAccountResult {
@@ -145,31 +150,9 @@ fn handle_settle_auction_complete(
145150
.into(),
146151
)
147152
} else {
148-
// Because the auction participant was penalized for executing the order
149-
// late, he will be deducted the base fee. This base fee will be sent to the
150-
// executor token account if it is not the same as the best offer token
151-
// account.
152-
153-
// We require that the executor token account be an ATA.
154-
require_keys_eq!(
155-
executor_token.key(),
156-
get_associated_token_address(
157-
&executor_token.owner,
158-
&executor_token.mint
159-
),
160-
ErrorCode::AccountNotAssociatedTokenAccount
161-
);
162-
163-
// And enforce that the owner of this ATA is the executor.
164-
require_keys_eq!(
165-
executor.key(),
166-
executor_token.owner,
167-
ErrorCode::ConstraintTokenOwner,
168-
);
169-
170153
(
171154
TokenAccountResult {
172-
balance_before: executor_token.amount,
155+
balance_before: base_fee_token_data.amount,
173156
amount: base_fee,
174157
}
175158
.into(),
@@ -183,7 +166,7 @@ fn handle_settle_auction_complete(
183166
}
184167
None => (
185168
TokenAccountResult {
186-
balance_before: executor_token.amount,
169+
balance_before: base_fee_token_data.amount,
187170
amount: repayment,
188171
}
189172
.into(),
@@ -193,7 +176,7 @@ fn handle_settle_auction_complete(
193176
}
194177
};
195178

196-
// Transfer executor his bounty if there are any.
179+
// Transfer base fee token his bounty if there are any.
197180
let settled_executor_result = match executor_result {
198181
Some(TokenAccountResult {
199182
balance_before,
@@ -204,7 +187,7 @@ fn handle_settle_auction_complete(
204187
token_program.to_account_info(),
205188
token::Transfer {
206189
from: prepared_custody_token.to_account_info(),
207-
to: executor_token.to_account_info(),
190+
to: base_fee_token.to_account_info(),
208191
authority: prepared_order_response.to_account_info(),
209192
},
210193
&[prepared_order_response_signer_seeds],
@@ -213,7 +196,7 @@ fn handle_settle_auction_complete(
213196
)?;
214197

215198
SettledTokenAccountInfo {
216-
key: executor_token.key(),
199+
key: base_fee_token.key(),
217200
balance_after: balance_before.saturating_add(amount),
218201
}
219202
.into()
@@ -252,7 +235,7 @@ fn handle_settle_auction_complete(
252235
emit!(crate::events::AuctionSettled {
253236
auction: ctx.accounts.auction.key(),
254237
best_offer_token: settled_best_offer_result,
255-
executor_token: settled_executor_result,
238+
base_fee_token: settled_executor_result,
256239
with_execute: Default::default(),
257240
});
258241

@@ -261,7 +244,7 @@ fn handle_settle_auction_complete(
261244
token_program.to_account_info(),
262245
token::CloseAccount {
263246
account: prepared_custody_token.to_account_info(),
264-
destination: executor.to_account_info(),
247+
destination: beneficiary.to_account_info(),
265248
authority: prepared_order_response.to_account_info(),
266249
},
267250
&[prepared_order_response_signer_seeds],

solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ fn settle_none_and_prepare_fill(accounts: SettleNoneAndPrepareFill<'_, '_>) -> R
8484
emit!(crate::events::AuctionSettled {
8585
auction: auction.key(),
8686
best_offer_token: Default::default(),
87-
executor_token: crate::events::SettledTokenAccountInfo {
87+
base_fee_token: crate::events::SettledTokenAccountInfo {
8888
key: fee_recipient_token.key(),
8989
balance_after: fee_recipient_token.amount.saturating_add(fee)
9090
}

solana/programs/matching-engine/src/state/prepared_order_response.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub struct PreparedOrderResponseSeeds {
1111
#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace)]
1212
pub struct PreparedOrderResponseInfo {
1313
pub prepared_by: Pubkey,
14+
pub base_fee_token: Pubkey,
1415

1516
pub fast_vaa_timestamp: u32,
1617
pub source_chain: u16,

solana/programs/matching-engine/src/utils/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub fn require_local_endpoint(endpoint: &RouterEndpoint) -> Result<bool> {
4545
pub fn checked_deserialize_token_account(
4646
acc_info: &AccountInfo,
4747
expected_mint: &Pubkey,
48-
) -> Option<token::TokenAccount> {
48+
) -> Option<Box<token::TokenAccount>> {
4949
if acc_info.owner != &token::ID {
5050
None
5151
} else {
@@ -54,5 +54,6 @@ pub fn checked_deserialize_token_account(
5454
token::TokenAccount::try_deserialize(&mut &data[..])
5555
.ok()
5656
.filter(|token_data| &token_data.mint == expected_mint && !token_data.is_frozen())
57+
.map(Box::new)
5758
}
5859
}

solana/ts/src/idl/json/matching_engine.json

+32-5
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,15 @@
13691369
"name": "prepared_custody_token",
13701370
"writable": true
13711371
},
1372+
{
1373+
"name": "base_fee_token",
1374+
"docs": [
1375+
"This token account will be the one that collects the base fee only if an auction's order",
1376+
"was executed late. Otherwise, the protocol's fee recipient token account will be used for",
1377+
"non-existent auctions and the best offer token account will be used for orders executed on",
1378+
"time."
1379+
]
1380+
},
13721381
{
13731382
"name": "usdc",
13741383
"accounts": [
@@ -1834,14 +1843,19 @@
18341843
],
18351844
"accounts": [
18361845
{
1837-
"name": "executor",
1846+
"name": "beneficiary",
18381847
"docs": [
18391848
"finalized VAA."
18401849
],
18411850
"writable": true
18421851
},
18431852
{
1844-
"name": "executor_token",
1853+
"name": "base_fee_token",
1854+
"docs": [
1855+
"This token account will receive the base fee only if there was a penalty when executing the",
1856+
"order. If it does not exist when there is a penalty, this instruction handler will revert.",
1857+
""
1858+
],
18451859
"writable": true
18461860
},
18471861
{
@@ -1851,7 +1865,7 @@
18511865
"signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent",
18521866
"to any account he chooses (this one).",
18531867
"",
1854-
"of the tokens to the executor token account."
1868+
"of the tokens to the base fee token account."
18551869
],
18561870
"writable": true
18571871
},
@@ -3032,6 +3046,14 @@
30323046
"code": 7082,
30333047
"name": "AuctionAlreadySettled"
30343048
},
3049+
{
3050+
"code": 7084,
3051+
"name": "InvalidBaseFeeToken"
3052+
},
3053+
{
3054+
"code": 7086,
3055+
"name": "BaseFeeTokenRequired"
3056+
},
30353057
{
30363058
"code": 7280,
30373059
"name": "CannotCloseAuctionYet"
@@ -3474,10 +3496,11 @@
34743496
}
34753497
},
34763498
{
3477-
"name": "executor_token",
3499+
"name": "base_fee_token",
34783500
"docs": [
34793501
"Depending on whether there was an active auction, this field will have the pubkey of the",
3480-
"executor token (if there was an auction) or fee recipient token (if there was no auction)."
3502+
"base fee token account (if there was an auction) or fee recipient token (if there was no",
3503+
"auction)."
34813504
],
34823505
"type": {
34833506
"option": {
@@ -4103,6 +4126,10 @@
41034126
"name": "prepared_by",
41044127
"type": "pubkey"
41054128
},
4129+
{
4130+
"name": "base_fee_token",
4131+
"type": "pubkey"
4132+
},
41064133
{
41074134
"name": "fast_vaa_timestamp",
41084135
"type": "u32"

0 commit comments

Comments
 (0)