Skip to content

Commit

Permalink
solana: fix ATA constraint for executor (#168)
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 8cfe602 commit b00b375
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 14 deletions.
4 changes: 4 additions & 0 deletions solana/programs/matching-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Matching Engine Program

A program to facilitate the transfer of USDC between networks that allow Wormhole and CCTP bridging.
With the help of solvers, allowing USDC to be transferred faster than finality.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
state::{Auction, AuctionStatus, PreparedOrderResponse},
};
use anchor_lang::prelude::*;
use anchor_spl::token;
use anchor_spl::{associated_token::get_associated_token_address, token};

#[derive(Accounts)]
pub struct SettleAuctionComplete<'info> {
Expand All @@ -16,8 +16,8 @@ pub struct SettleAuctionComplete<'info> {

#[account(
mut,
associated_token::mint = best_offer_token.mint,
associated_token::authority = executor,
token::mint = best_offer_token.mint,
token::authority = executor,
)]
executor_token: Account<'info, token::TokenAccount>,

Expand Down Expand Up @@ -89,17 +89,18 @@ fn handle_settle_auction_complete(
total_penalty: execute_penalty.map(|v| v.saturating_add(base_fee)),
};

let executor_token = &ctx.accounts.executor_token;
let best_offer_token = &ctx.accounts.best_offer_token;
let prepared_custody_token = &ctx.accounts.prepared_custody_token;
let token_program = &ctx.accounts.token_program;

let prepared_order_response_signer_seeds = &[
PreparedOrderResponse::SEED_PREFIX,
prepared_order_response.seeds.fast_vaa_hash.as_ref(),
&[prepared_order_response.seeds.bump],
];

let executor = &ctx.accounts.executor;
let executor_token = &ctx.accounts.executor_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;

// We may deduct from this account if the winning participant was penalized.
let mut repayment = ctx.accounts.prepared_custody_token.amount;

Expand All @@ -116,7 +117,7 @@ fn handle_settle_auction_complete(
executor_token.key(),
best_offer_token.key(),
MatchingEngineError::ExecutorTokenMismatch
)
);
}
_ => {
// If there is a penalty, we want to return the lamports back to the person who paid to
Expand All @@ -125,7 +126,7 @@ fn handle_settle_auction_complete(
// The executor's intention here would be to collect the base fee to cover the cost to
// post the finalized VAA.
require_keys_eq!(
executor_token.owner,
executor.key(),
prepared_order_response.prepared_by,
MatchingEngineError::ExecutorNotPreparedBy
);
Expand All @@ -134,6 +135,15 @@ fn handle_settle_auction_complete(
// 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
);

// Transfer base fee to the executor.
token::transfer(
CpiContext::new_with_signer(
token_program.to_account_info(),
Expand Down Expand Up @@ -178,7 +188,7 @@ fn handle_settle_auction_complete(
token_program.to_account_info(),
token::CloseAccount {
account: prepared_custody_token.to_account_info(),
destination: ctx.accounts.executor.to_account_info(),
destination: executor.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 @@ -88,7 +88,7 @@ impl AccountDeserialize for AuctionHistoryInternal {
}

fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
*buf = &mut &buf[8..];
*buf = &buf[8..];
Ok(Self {
header: AnchorDeserialize::deserialize(buf)?,
num_entries: AnchorDeserialize::deserialize(buf)?,
Expand Down
4 changes: 4 additions & 0 deletions solana/programs/token-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Token Router Program

A program which is used to send and receive USDC from other networks via CCTP accompanied by a
Wormhole message.
5 changes: 5 additions & 0 deletions solana/programs/upgrade-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Upgrade Manager Program

This program is used to perform upgrades for the Matching Engine and Token Router programs.

Only the owner of these programs can perform these upgrades.
6 changes: 4 additions & 2 deletions solana/ts/src/matchingEngine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1594,10 +1594,12 @@ export class MatchingEngineProgram {
preparedOrderResponse: PublicKey;
auction?: PublicKey;
bestOfferToken?: PublicKey;
executorToken?: PublicKey;
}) {
const { executor, preparedOrderResponse } = accounts;

let { auction, bestOfferToken } = accounts;
let { auction, bestOfferToken, executorToken } = accounts;
executorToken ??= splToken.getAssociatedTokenAddressSync(this.mint, executor);

if (auction === undefined) {
const { seeds } = await this.fetchPreparedOrderResponse({
Expand All @@ -1620,7 +1622,7 @@ export class MatchingEngineProgram {
.settleAuctionComplete()
.accounts({
executor,
executorToken: splToken.getAssociatedTokenAddressSync(this.mint, executor),
executorToken,
preparedOrderResponse,
preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrderResponse),
auction,
Expand Down
39 changes: 39 additions & 0 deletions solana/ts/tests/01__matchingEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3670,6 +3670,44 @@ describe("Matching Engine", function () {
);
});

it("Cannot Settle Completed with Penalty (Executor is not ATA)", async function () {
const executorTokenSigner = Keypair.generate();
const executorToken = executorTokenSigner.publicKey;

await expectIxOk(
connection,
[
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: executorToken,
lamports: await connection.getMinimumBalanceForRentExemption(
splToken.ACCOUNT_SIZE,
),
space: splToken.ACCOUNT_SIZE,
programId: splToken.TOKEN_PROGRAM_ID,
}),
splToken.createInitializeAccount3Instruction(
executorToken,
engine.mint,
playerTwo.publicKey,
),
],
[payer, executorTokenSigner],
);

await settleAuctionCompleteForTest(
{
executor: playerTwo.publicKey,
executorToken,
},
{
prepareSigners: [playerTwo],
executeWithinGracePeriod: false,
errorMsg: "Error Code: AccountNotAssociatedTokenAccount",
},
);
});

it("Settle Completed with Penalty (Executor != Best Offer)", async function () {
await settleAuctionCompleteForTest(
{
Expand Down Expand Up @@ -4744,6 +4782,7 @@ describe("Matching Engine", function () {
async function settleAuctionCompleteForTest(
accounts: {
executor?: PublicKey;
executorToken?: PublicKey;
preparedOrderResponse?: PublicKey;
auction?: PublicKey;
bestOfferToken?: PublicKey;
Expand Down

0 comments on commit b00b375

Please sign in to comment.