From b00b37584bc30292837f28523354e91252d375f0 Mon Sep 17 00:00:00 2001 From: A5 Pickle <5342825+a5-pickle@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:10:05 -0500 Subject: [PATCH] solana: fix ATA constraint for executor (#168) Co-authored-by: A5 Pickle --- solana/programs/matching-engine/README.md | 4 ++ .../src/processor/auction/settle/complete.rs | 32 +++++++++------ .../src/state/auction_history.rs | 2 +- solana/programs/token-router/README.md | 4 ++ solana/programs/upgrade-manager/README.md | 5 +++ solana/ts/src/matchingEngine/index.ts | 6 ++- solana/ts/tests/01__matchingEngine.ts | 39 +++++++++++++++++++ 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/solana/programs/matching-engine/README.md b/solana/programs/matching-engine/README.md index e69de29b..895eb3b2 100644 --- a/solana/programs/matching-engine/README.md +++ b/solana/programs/matching-engine/README.md @@ -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. diff --git a/solana/programs/matching-engine/src/processor/auction/settle/complete.rs b/solana/programs/matching-engine/src/processor/auction/settle/complete.rs index 8d812ab5..9c64cc27 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/complete.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/complete.rs @@ -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> { @@ -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>, @@ -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; @@ -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 @@ -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 ); @@ -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(), @@ -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], diff --git a/solana/programs/matching-engine/src/state/auction_history.rs b/solana/programs/matching-engine/src/state/auction_history.rs index be00e889..d0293eab 100644 --- a/solana/programs/matching-engine/src/state/auction_history.rs +++ b/solana/programs/matching-engine/src/state/auction_history.rs @@ -88,7 +88,7 @@ impl AccountDeserialize for AuctionHistoryInternal { } fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { - *buf = &mut &buf[8..]; + *buf = &buf[8..]; Ok(Self { header: AnchorDeserialize::deserialize(buf)?, num_entries: AnchorDeserialize::deserialize(buf)?, diff --git a/solana/programs/token-router/README.md b/solana/programs/token-router/README.md index e69de29b..bbbfd945 100644 --- a/solana/programs/token-router/README.md +++ b/solana/programs/token-router/README.md @@ -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. diff --git a/solana/programs/upgrade-manager/README.md b/solana/programs/upgrade-manager/README.md index e69de29b..7fe5b9f8 100644 --- a/solana/programs/upgrade-manager/README.md +++ b/solana/programs/upgrade-manager/README.md @@ -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. diff --git a/solana/ts/src/matchingEngine/index.ts b/solana/ts/src/matchingEngine/index.ts index e064fa24..52d0b8ef 100644 --- a/solana/ts/src/matchingEngine/index.ts +++ b/solana/ts/src/matchingEngine/index.ts @@ -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({ @@ -1620,7 +1622,7 @@ export class MatchingEngineProgram { .settleAuctionComplete() .accounts({ executor, - executorToken: splToken.getAssociatedTokenAddressSync(this.mint, executor), + executorToken, preparedOrderResponse, preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrderResponse), auction, diff --git a/solana/ts/tests/01__matchingEngine.ts b/solana/ts/tests/01__matchingEngine.ts index 4a47e4b0..23234470 100644 --- a/solana/ts/tests/01__matchingEngine.ts +++ b/solana/ts/tests/01__matchingEngine.ts @@ -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( { @@ -4744,6 +4782,7 @@ describe("Matching Engine", function () { async function settleAuctionCompleteForTest( accounts: { executor?: PublicKey; + executorToken?: PublicKey; preparedOrderResponse?: PublicKey; auction?: PublicKey; bestOfferToken?: PublicKey;