From 0760b57ec4cd5abc8ae2b347d0b1ef3ad73bd7e3 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 15:05:45 +0000 Subject: [PATCH 01/14] basic server implementation --- auction-server/api-types/src/opportunity.rs | 7 +++++++ auction-server/src/auction/service/verification.rs | 6 ++++++ auction-server/src/opportunity/entities/opportunity_svm.rs | 4 ++++ auction-server/src/opportunity/entities/quote.rs | 2 ++ auction-server/src/opportunity/repository/models.rs | 1 + auction-server/src/opportunity/service/get_quote.rs | 1 + 6 files changed, 21 insertions(+) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index eb14b3610..66be24715 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -344,6 +344,10 @@ pub enum OpportunityParamsV1ProgramSvm { /// Details about which token accounts need to be initialized and by whom token_account_initialization_configs: TokenAccountInitializationConfigs, + + /// If provided, this memo must be included in the bid transaction as a Memo program instruction. + #[schema(example = "memo")] + memo: Option, }, } @@ -575,6 +579,9 @@ pub struct QuoteCreateV1SvmParams { /// The chain id for creating the quote. #[schema(example = "solana", value_type = String)] pub chain_id: ChainId, + /// Optional memo to be included in the transaction + #[schema(example = "memo")] + pub memo: Option, } #[serde_as] diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 97493f411..1e9a76042 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1965,6 +1965,7 @@ mod tests { user_mint_user_balance: 0, token_account_initialization_config: TokenAccountInitializationConfigs::searcher_payer(), + memo: None, }), }; @@ -2000,6 +2001,7 @@ mod tests { user_mint_user_balance: 0, token_account_initialization_config: TokenAccountInitializationConfigs::searcher_payer(), + memo: None, }), }; @@ -2037,6 +2039,7 @@ mod tests { user_ata_mint_user: TokenAccountInitializationConfig::SearcherPayer, ..TokenAccountInitializationConfigs::searcher_payer() }, + memo: None, }), }; @@ -2072,6 +2075,7 @@ mod tests { user_mint_user_balance: 0, token_account_initialization_config: TokenAccountInitializationConfigs::searcher_payer(), + memo: None, }), }; @@ -2116,6 +2120,7 @@ mod tests { user_mint_user_balance: 0, token_account_initialization_config: TokenAccountInitializationConfigs::searcher_payer(), + memo: None, }), }; @@ -2155,6 +2160,7 @@ mod tests { user_ata_mint_searcher: TokenAccountInitializationConfig::UserPayer, ..TokenAccountInitializationConfigs::searcher_payer() }, + memo: None, }), }; diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index ed8f13ec4..ced498dfa 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -100,6 +100,7 @@ pub struct OpportunitySvmProgramSwap { pub token_program_user: Pubkey, pub token_program_searcher: Pubkey, pub token_account_initialization_config: TokenAccountInitializationConfigs, + pub memo: Option, } #[derive(Debug, Clone, PartialEq)] @@ -168,6 +169,7 @@ impl Opportunity for OpportunitySvm { token_account_initialization_config: program .token_account_initialization_config, user_mint_user_balance: program.user_mint_user_balance, + memo: program.memo, }, ) } @@ -368,6 +370,7 @@ impl From for api::OpportunitySvm { token_account_initialization_configs: program .token_account_initialization_config .into(), + memo: program.memo, } } }; @@ -423,6 +426,7 @@ impl TryFrom> for Op token_account_initialization_config: program .token_account_initialization_config, user_mint_user_balance: program.user_mint_user_balance, + memo: program.memo, }) } }; diff --git a/auction-server/src/opportunity/entities/quote.rs b/auction-server/src/opportunity/entities/quote.rs index da4850d06..ae011210b 100644 --- a/auction-server/src/opportunity/entities/quote.rs +++ b/auction-server/src/opportunity/entities/quote.rs @@ -40,6 +40,7 @@ pub struct QuoteCreate { pub tokens: QuoteTokens, pub referral_fee_info: Option, pub chain_id: ChainId, + pub memo: Option, } @@ -126,6 +127,7 @@ impl From for QuoteCreate { tokens, referral_fee_info, chain_id: params.chain_id, + memo: params.memo, } } } diff --git a/auction-server/src/opportunity/repository/models.rs b/auction-server/src/opportunity/repository/models.rs index d0268e597..9e55ec270 100644 --- a/auction-server/src/opportunity/repository/models.rs +++ b/auction-server/src/opportunity/repository/models.rs @@ -98,6 +98,7 @@ pub struct OpportunityMetadataSvmProgramSwap { pub token_program_searcher: Pubkey, pub user_mint_user_balance: u64, pub token_account_initialization_config: TokenAccountInitializationConfigs, + pub memo: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index b2a269bf6..9db78a46f 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -348,6 +348,7 @@ impl Service { user_mint_user_balance, token_account_initialization_config, token_program_searcher, + memo: quote_create.memo, }); Ok(entities::OpportunityCreateSvm { From 8042b9b44a25b6814ac108c9901fb5d6783d7686 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 15:31:38 +0000 Subject: [PATCH 02/14] update a bunch of files --- Cargo.lock | 24 ++++++++++++++++++++++++ Cargo.toml | 1 + sdk/js/src/index.ts | 1 + sdk/js/src/serverTypes.d.ts | 10 ++++++++++ sdk/js/src/types.ts | 5 +++++ sdk/rust/Cargo.toml | 1 + sdk/rust/src/lib.rs | 4 ++++ sdk/rust/src/svm.rs | 7 ++++++- tilt-scripts/svm/test_swap.py | 1 + 9 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 631bcb835..7f12dd6e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2585,6 +2585,7 @@ dependencies = [ "solana-rpc-client", "solana-sdk", "spl-associated-token-account", + "spl-memo-client", "spl-token", "tokio", "tokio-stream", @@ -3820,6 +3821,15 @@ dependencies = [ "signature 2.2.0", ] +[[package]] +name = "kaigan" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba15de5aeb137f0f65aa3bf82187647f1285abfe5b20c80c2c37f7007ad519a" +dependencies = [ + "borsh 0.10.4", +] + [[package]] name = "keccak" version = "0.1.5" @@ -8907,6 +8917,20 @@ dependencies = [ "solana-pubkey", ] +[[package]] +name = "spl-memo-client" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce6e135e27600f522db39d8e48eb73cb16a85ad08fa7b282ea4b6356919940" +dependencies = [ + "borsh 0.10.4", + "kaigan", + "num-derive", + "num-traits", + "solana-program", + "thiserror 2.0.12", +] + [[package]] name = "spl-pod" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index e3ef2db49..8f55af601 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ litesvm = "0.6.0" borsh = "1.5.1" spl-associated-token-account = "6.0.0" spl-token = "7.0.0" +spl-memo-client = "0.1.0" # This patch disables debugging features in litesvm runtime_environments # which allows more programs to be loaded in the runtime diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 32dd1d864..796afe034 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -519,6 +519,7 @@ export class Client { user_wallet_address: quoteRequest.userWallet ? quoteRequest.userWallet.toBase58() : null, + memo: quoteRequest.memo ?? null, version: "v1" as const, }; // TODO: we may want to wrap all the GET/POST calls in a try/catch block to handle errors diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 5ce9d77df..bfbeb8443 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -796,6 +796,11 @@ export interface components { | { /** @description Specifies whether the fees are to be paid in the searcher or user token. */ fee_token: components["schemas"]["FeeToken"]; + /** + * @description If provided, this memo must be included in the bid transaction as a Memo program instruction. + * @example memo + */ + memo?: string | null; /** * @description The permission account that serves as an identifier for the swap opportunity. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -886,6 +891,11 @@ export interface components { * @example EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v */ input_token_mint: string; + /** + * @description Optional memo to be included in the transaction + * @example memo + */ + memo?: string | null; /** * @description The mint address of the token the user will receive in the swap. * @example EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index c99924de0..5d1c24c01 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -404,6 +404,11 @@ export type QuoteRequest = { * @example 11111111111111111111111111111111 */ userWallet?: PublicKey; + /** + * @description Optional memo to be included in the transaction. + * @example "memo" + */ + memo?: string; }; export type QuoteResponse = { diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index 0d6a72d54..f27eecc17 100644 --- a/sdk/rust/Cargo.toml +++ b/sdk/rust/Cargo.toml @@ -23,3 +23,4 @@ borsh = { workspace = true } spl-associated-token-account = { workspace = true } spl-token = { workspace = true } express-relay = { version = "0.7.0", path = "../../contracts/svm/programs/express_relay" } +spl-memo-client = { workspace = true } diff --git a/sdk/rust/src/lib.rs b/sdk/rust/src/lib.rs index df64036d6..a19f53c64 100644 --- a/sdk/rust/src/lib.rs +++ b/sdk/rust/src/lib.rs @@ -782,6 +782,7 @@ impl Biddable for api_types::opportunity::OpportunitySvm { router_account, referral_fee_bps, token_account_initialization_configs, + memo, .. } => { let _ = match params.program_params { @@ -816,6 +817,9 @@ impl Biddable for api_types::opportunity::OpportunitySvm { FeeToken::UserToken => (user_token, tokens.token_program_user), }; let mut instructions = params.instructions; + if let Some(memo) = memo { + instructions.push(svm::Svm::get_memo_instruction(memo)); + } instructions.extend(svm::Svm::get_swap_create_accounts_idempotent_instructions( svm::GetSwapCreateAccountsIdempotentInstructionsParams { searcher: params.searcher, diff --git a/sdk/rust/src/svm.rs b/sdk/rust/src/svm.rs index 9d629fd79..89ac840d5 100644 --- a/sdk/rust/src/svm.rs +++ b/sdk/rust/src/svm.rs @@ -133,7 +133,6 @@ struct OpportunitySwapData<'a> { referral_fee_bps: &'a u16, platform_fee_bps: &'a u64, } - pub struct GetSwapCreateAccountsIdempotentInstructionsParams { pub searcher: Pubkey, pub user: Pubkey, @@ -325,6 +324,12 @@ impl Svm { } } + pub fn get_memo_instruction(memo: String) -> Instruction { + spl_memo_client::instructions::AddMemoBuilder::new() + .memo(memo.into()) + .instruction() + } + pub fn get_swap_create_accounts_idempotent_instructions( params: GetSwapCreateAccountsIdempotentInstructionsParams, ) -> Vec { diff --git a/tilt-scripts/svm/test_swap.py b/tilt-scripts/svm/test_swap.py index 0f82ed1b0..f522d14e3 100644 --- a/tilt-scripts/svm/test_swap.py +++ b/tilt-scripts/svm/test_swap.py @@ -63,6 +63,7 @@ async def send_and_submit_quote(server_url, kp_taker, input_token, output_token, "referral_fee_bps": 10, "specified_token_amount": {"amount": random.randint(1, 1000), "side": side}, "user_wallet_address": str(pk_taker), + "memo": "memo", "version": "v1", } From 2b067fd07dec289450b37d786d1891fdf153f5c2 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 15:44:36 +0000 Subject: [PATCH 03/14] add to js sdk --- pnpm-lock.yaml | 17 +++++++++++++++++ sdk/js/package.json | 3 ++- sdk/js/src/index.ts | 1 + sdk/js/src/svm.ts | 8 ++++++++ sdk/js/src/types.ts | 1 + 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd94a0bb8..df5f249c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -362,6 +362,9 @@ importers: "@kamino-finance/limo-sdk": specifier: "catalog:" version: 1.0.8(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.4)(utf-8-validate@5.0.10) + "@solana/spl-memo": + specifier: ^0.2.5 + version: 0.2.5(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) "@solana/spl-token": specifier: "catalog:" version: 0.4.9(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.4)(utf-8-validate@5.0.10) @@ -3168,6 +3171,15 @@ packages: peerDependencies: typescript: ">=5" + "@solana/spl-memo@0.2.5": + resolution: + { + integrity: sha512-0Zx5t3gAdcHlRTt2O3RgGlni1x7vV7Xq7j4z9q8kKOMgU03PyoTbFQ/BSYCcICHzkaqD7ZxAiaJ6dlXolg01oA==, + } + engines: { node: ">=16" } + peerDependencies: + "@solana/web3.js": ^1.91.6 + "@solana/spl-token-group@0.0.7": resolution: { @@ -15421,6 +15433,11 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + "@solana/spl-memo@0.2.5(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))": + dependencies: + "@solana/web3.js": 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + buffer: 6.0.3 + "@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.4)": dependencies: "@solana/codecs": 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.4) diff --git a/sdk/js/package.json b/sdk/js/package.json index a036d30a0..5cfb9f95f 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -40,8 +40,9 @@ "dependencies": { "@coral-xyz/anchor": "catalog:", "@kamino-finance/limo-sdk": "catalog:", - "@solana/web3.js": "catalog:", + "@solana/spl-memo": "^0.2.5", "@solana/spl-token": "catalog:", + "@solana/web3.js": "catalog:", "bs58": "catalog:", "decimal.js": "^10.4.3", "isomorphic-ws": "^5.0.0", diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 796afe034..c25d58a13 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -726,6 +726,7 @@ export class Client { userAtaMintUser: opportunity.token_account_initialization_configs.user_ata_mint_user, }, + memo: opportunity.memo ?? undefined, }; } else { console.warn("Unsupported opportunity", opportunity); diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index afa7bbb5e..03969601a 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -29,6 +29,7 @@ import { ExpressRelay } from "./expressRelayTypes"; import expressRelayIdl from "./idl/idlExpressRelay.json"; import { SVM_CONSTANTS } from "./const"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; +import { createMemoInstruction } from "@solana/spl-memo"; function getExpressRelayProgram(chain: string): PublicKey { if (!SVM_CONSTANTS[chain]) { @@ -248,6 +249,7 @@ function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { searcherToken: PublicKey; tokenProgramSearcher: PublicKey; tokenInitializationConfigs: TokenAccountInitializationConfigs; + memo?: string; } { const tokenProgramSearcher = swapOpportunity.tokens.tokenProgramSearcher; const tokenProgramUser = swapOpportunity.tokens.tokenProgramUser; @@ -260,6 +262,7 @@ function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { : [userToken, tokenProgramUser]; const router = swapOpportunity.routerAccount; const tokenInitializationConfigs = swapOpportunity.tokenInitializationConfigs; + const memo = swapOpportunity.memo; return { searcherToken, tokenProgramSearcher, @@ -270,6 +273,7 @@ function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { feeTokenProgram, router, tokenInitializationConfigs, + memo, }; } @@ -435,6 +439,10 @@ export async function constructSwapBid( const { userToken, searcherToken, user, tokenInitializationConfigs } = extractSwapInfo(swapOpportunity); + if (swapOpportunity.memo) { + tx.instructions.push(createMemoInstruction(swapOpportunity.memo)); + } + const tokenAccountsToCreate = getTokenAccountsToCreate( searcher, swapOpportunity, diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 5d1c24c01..01bf71cac 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -164,6 +164,7 @@ export type OpportunitySvmSwap = { tokens: SvmSwapTokens; program: "swap"; tokenInitializationConfigs: TokenAccountInitializationConfigs; + memo?: string; } & OpportunitySvmMetadata; export type OpportunitySvm = OpportunitySvmLimo | OpportunitySvmSwap; From 979871d36fd175c8970b39bf08178ba7031e9188 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 15:55:56 +0000 Subject: [PATCH 04/14] python --- sdk/python/express_relay/client.py | 10 ++++++++++ sdk/python/express_relay/models/svm.py | 1 + 2 files changed, 11 insertions(+) diff --git a/sdk/python/express_relay/client.py b/sdk/python/express_relay/client.py index 3eb9d23ff..ec160f620 100644 --- a/sdk/python/express_relay/client.py +++ b/sdk/python/express_relay/client.py @@ -69,6 +69,7 @@ from solders.instruction import Instruction from solders.pubkey import Pubkey from solders.sysvar import INSTRUCTIONS +from spl.memo.constants import MEMO_PROGRAM_ID from spl.token.constants import WRAPPED_SOL_MINT from websockets.client import WebSocketClientProtocol @@ -720,6 +721,15 @@ def get_svm_swap_instructions( instructions: List[Instruction] = [] + if swap_opportunity.memo is not None: + instructions.append( + Instruction( + program_id=MEMO_PROGRAM_ID, + accounts=[], + data=swap_opportunity.memo.encode(), + ) + ) + token_accounts_to_create = ExpressRelayClient.get_token_accounts_to_create( searcher=searcher, swap_opportunity=swap_opportunity, diff --git a/sdk/python/express_relay/models/svm.py b/sdk/python/express_relay/models/svm.py index e8d70d407..d5560c7df 100644 --- a/sdk/python/express_relay/models/svm.py +++ b/sdk/python/express_relay/models/svm.py @@ -362,6 +362,7 @@ class SwapOpportunitySvm(BaseOpportunitySvm): user_wallet_address: SvmAddress tokens: SwapTokensSearcherSpecified | SwapTokensUserSpecified token_account_initialization_configs: TokenAccountInitializationConfigs + memo: str | None = Field(default=None) OpportunitySvm = SwapOpportunitySvm | LimoOpportunitySvm From dfef94abdaf2352af771f991f58d5be53e25f669 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:01:37 +0000 Subject: [PATCH 05/14] refactor instruction extractor --- Cargo.lock | 1 + auction-server/Cargo.toml | 1 + .../src/auction/service/verification.rs | 105 +++++++----------- 3 files changed, 44 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f12dd6e8..9ac5de5fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -707,6 +707,7 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "spl-associated-token-account", + "spl-memo-client", "spl-token", "sqlx", "strum", diff --git a/auction-server/Cargo.toml b/auction-server/Cargo.toml index 32bbdc34d..888d81155 100644 --- a/auction-server/Cargo.toml +++ b/auction-server/Cargo.toml @@ -57,6 +57,7 @@ strum.workspace = true spl-associated-token-account = { workspace = true } spl-token = { workspace = true } mockall_double = "0.3.1" +spl-memo-client = { workspace = true } [dev-dependencies] mockall = "0.13.1" diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 1e9a76042..bda85d32e 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -495,17 +495,13 @@ impl Service { } BidPaymentInstructionType::Swap => express_relay_svm::instruction::Swap::DISCRIMINATOR, }; - let instructions = transaction - .message - .instructions() - .iter() - .enumerate() - .filter(|(_, instruction)| { - instruction.program_id(transaction.message.static_account_keys()) - == &self.config.chain_config.express_relay.program_id - }) - .map(|(index, instruction)| (index, instruction.clone())) - .collect::>(); + let instructions = Self::extract_program_instructions( + &transaction, + &self.config.chain_config.express_relay.program_id, + ) + .into_iter() + .map(|(index, instruction)| (index, instruction.clone())) + .collect::>(); let (instruction_index, instruction) = match instructions.len() { 1 => Ok(instructions @@ -612,6 +608,8 @@ impl Service { } } else if *program_id == self.config.chain_config.express_relay.program_id { Ok(()) + } else if *program_id == spl_memo_client::ID { + Ok(()) } else { Err(InstructionError::UnsupportedProgram(*program_id)) } @@ -796,21 +794,16 @@ impl Service { &self, tx: &VersionedTransaction, ) -> Result, RestError> { - let instructions: Vec<(usize, &CompiledInstruction)> = tx - .message - .instructions() - .iter() - .enumerate() - .filter(|(_, instruction)| { - instruction.program_id(tx.message.static_account_keys()) == &system_program::id() - }) - .filter(|(_, instruction)| { - matches!( - bincode::deserialize::(&instruction.data), - Ok(SystemInstruction::Transfer { .. }) - ) - }) - .collect(); + let instructions: Vec<(usize, &CompiledInstruction)> = + Self::extract_program_instructions(tx, &system_program::id()) + .into_iter() + .filter(|(_, instruction)| { + matches!( + bincode::deserialize::(&instruction.data), + Ok(SystemInstruction::Transfer { .. }) + ) + }) + .collect(); let mut result = vec![]; for (index, instruction) in instructions { let data = @@ -934,33 +927,23 @@ impl Service { Ok(()) } - fn extract_token_instructions(tx: &VersionedTransaction) -> Vec<(usize, &CompiledInstruction)> { + fn extract_program_instructions<'a>( + tx: &'a VersionedTransaction, + program_id: &Pubkey, + ) -> Vec<(usize, &'a CompiledInstruction)> { tx.message .instructions() .iter() .enumerate() .filter(|(_, instruction)| { - instruction.program_id(tx.message.static_account_keys()) == &spl_token::id() + instruction.program_id(tx.message.static_account_keys()) == program_id }) .collect() } - fn extract_associated_token_account_instructions( - tx: &VersionedTransaction, - ) -> Vec<(usize, &CompiledInstruction)> { - tx.message - .instructions() - .iter() - .enumerate() - .filter(|(_, instruction)| { - instruction.program_id(tx.message.static_account_keys()) - == &spl_associated_token_account::id() - }) - .collect() - } fn extract_sync_native_instructions(tx: &VersionedTransaction) -> Vec<&CompiledInstruction> { - let token_instructions = Self::extract_token_instructions(tx); + let token_instructions = Self::extract_program_instructions(tx, &spl_token::id()); token_instructions .into_iter() .filter_map(|(_, instruction)| { @@ -1003,7 +986,7 @@ impl Service { tx: &VersionedTransaction, ) -> Result, RestError> { let mut result = vec![]; - for (index, instruction) in Self::extract_token_instructions(tx) { + for (index, instruction) in Self::extract_program_instructions(tx, &spl_token::id()) { let ix_parsed = TokenInstruction::unpack(&instruction.data).ok(); if let Some(TokenInstruction::CloseAccount) = ix_parsed { let accounts = futures::future::try_join_all( @@ -1028,7 +1011,9 @@ impl Service { tx: &VersionedTransaction, ) -> Result, RestError> { let mut result = vec![]; - for (index, instruction) in Self::extract_associated_token_account_instructions(tx) { + for (index, instruction) in + Self::extract_program_instructions(tx, &spl_associated_token_account::id()) + { let ix_parsed = AssociatedTokenAccountInstruction::try_from_slice(&instruction.data).ok(); if matches!( @@ -1175,7 +1160,6 @@ impl Service { Ok(()) } - async fn check_create_ata_instructions( &self, tx: &VersionedTransaction, @@ -1643,25 +1627,20 @@ impl Service { .min() .unwrap_or(0); - let budgets: Vec = transaction - .message - .instructions() - .iter() - .filter_map(|instruction| { - if instruction.program_id(transaction.message.static_account_keys()) - != &compute_budget::id() - { - return None; - } - - match compute_budget::ComputeBudgetInstruction::try_from_slice(&instruction.data) { - Ok(compute_budget::ComputeBudgetInstruction::SetComputeUnitPrice(price)) => { - Some(price) + let budgets: Vec = + Self::extract_program_instructions(&transaction, &compute_budget::id()) + .into_iter() + .filter_map(|(_, instruction)| { + match compute_budget::ComputeBudgetInstruction::try_from_slice( + &instruction.data, + ) { + Ok(compute_budget::ComputeBudgetInstruction::SetComputeUnitPrice( + price, + )) => Some(price), + _ => None, } - _ => None, - } - }) - .collect(); + }) + .collect(); if budgets.len() > 1 { return Err(RestError::MultipleSetComputeUnitPriceInstructions); } From c3da0c3a47102ab3cc8674b03f56145e5f3e3aa9 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:02:39 +0000 Subject: [PATCH 06/14] refactor instruction extractor --- auction-server/src/auction/service/verification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index bda85d32e..6329cbb71 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1628,7 +1628,7 @@ impl Service { .unwrap_or(0); let budgets: Vec = - Self::extract_program_instructions(&transaction, &compute_budget::id()) + Self::extract_program_instructions(transaction, &compute_budget::id()) .into_iter() .filter_map(|(_, instruction)| { match compute_budget::ComputeBudgetInstruction::try_from_slice( From fcc4dcd5503c001a4847592db629d2f0e36fc8a1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:04:35 +0000 Subject: [PATCH 07/14] refactor instruction extractor --- auction-server/src/auction/service/verification.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 6329cbb71..4ddaa390c 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -594,8 +594,6 @@ impl Service { ix_parsed ))), } - } else if *program_id == compute_budget::id() { - Ok(()) } else if *program_id == spl_associated_token_account::id() { let ix_parsed = AssociatedTokenAccountInstruction::try_from_slice(&ix.data).map_err(|e| { @@ -606,9 +604,10 @@ impl Service { AssociatedTokenAccountInstruction::CreateIdempotent => Ok(()), _ => Err(InstructionError::UnsupportedAssociatedTokenAccountInstruction(ix_parsed)), } - } else if *program_id == self.config.chain_config.express_relay.program_id { - Ok(()) - } else if *program_id == spl_memo_client::ID { + } else if *program_id == self.config.chain_config.express_relay.program_id + || *program_id == spl_memo_client::ID + || *program_id == compute_budget::id() + { Ok(()) } else { Err(InstructionError::UnsupportedProgram(*program_id)) From a64954a01176444abbdb12d1e09eabd3d3f6a69c Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:21:28 +0000 Subject: [PATCH 08/14] add memo verif --- auction-server/src/api.rs | 16 +++++++++ .../src/auction/service/verification.rs | 34 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index 5b2180e16..38c17bfc4 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -126,6 +126,8 @@ pub enum InstructionError { InvalidTokenProgramInCreateAtaInstruction { expected: Pubkey, found: Pubkey }, InvalidSystemProgramInCreateAtaInstruction(Pubkey), MissingCreateAtaInstruction(Pubkey), + InvalidMemoInstructionCount { expected: usize, found: usize }, + InvalidMemoString { expected: String, found: String }, } impl std::fmt::Display for InstructionError { @@ -258,6 +260,20 @@ impl std::fmt::Display for InstructionError { ata ) } + InstructionError::InvalidMemoInstructionCount { expected, found } => { + write!( + f, + "Invalid memo instruction count. Expected: {:?} found: {:?}", + expected, found + ) + } + InstructionError::InvalidMemoString { expected, found } => { + write!( + f, + "Invalid memo string in memo instruction. Expected: {:?} found: {:?}", + expected, found + ) + } } } } diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 4ddaa390c..688d2fed1 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1159,6 +1159,37 @@ impl Service { Ok(()) } + async fn check_memo_instructions( + tx: &VersionedTransaction, + memo: &Option, + ) -> Result<(), RestError> { + let memo_instructions = Self::extract_program_instructions(tx, &spl_memo_client::ID); + match (memo, memo_instructions.len()) { + (_, 0) => Ok(()), // todo: this is for backward compatibility, we can fix this once searchers have updated their sdk + (Some(memo), 1) => { + let (index, instruction) = memo_instructions[0]; // safe to index because we checked the length + if instruction.data != memo.as_bytes() { + return Err(RestError::InvalidInstruction( + Some(index), + InstructionError::InvalidMemoString { + expected: memo.clone(), + found: String::from_utf8(instruction.data.clone()) + .unwrap_or_default(), + }, + )); + } + Ok(()) + } + (_, _) => Err(RestError::InvalidInstruction( + None, + InstructionError::InvalidMemoInstructionCount { + expected: memo.as_ref().map_or(0, |_| 1), + found: memo_instructions.len(), + }, + )), + } + } + async fn check_create_ata_instructions( &self, tx: &VersionedTransaction, @@ -1365,6 +1396,9 @@ impl Service { .. } = swap_accounts.clone(); + Self::check_memo_instructions(&bid_data.transaction, &opportunity_swap_data.memo) + .await?; + self.check_create_ata_instructions( &bid_data.transaction, &swap_accounts, From 2d277906e22ed96ad6444d1d66a3fac8e666e73d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:31:25 +0000 Subject: [PATCH 09/14] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4584452c6..ff1763d5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the searcher sdks will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [Unreleased] + +### Added + +- For swap opportunities, the searcher sdks will now add a memo instruction to the bid transaction if the quote requester so desires. This allows the quote requester to track which on-chain transactions correspond to quotes they requested. [458](https://github.com/pyth-network/per/pull/458) + ## [Rust: 0.7.0, Python 0.22.0, Javascript 0.23.0] - 2025-03-25 ### Changed From 201fddc1c0a09911b7724bf1b9383eebcefcf794 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:45:58 +0000 Subject: [PATCH 10/14] add tests --- .../src/auction/service/verification.rs | 140 +++++++++++++++++- 1 file changed, 134 insertions(+), 6 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 688d2fed1..66dcfd667 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -2138,6 +2138,45 @@ mod tests { let opp_with_user_payer = OpportunitySvm { + core_fields: OpportunityCoreFields:: { + id: Uuid::new_v4(), + permission_key: OpportunitySvm::get_permission_key( + BidPaymentInstructionType::Swap, + router, + permission_account_user_token_specified, + ), + chain_id: chain_id.clone(), + sell_tokens: vec![TokenAmountSvm { + token: searcher_token_address, + amount: 0, + }], + buy_tokens: vec![TokenAmountSvm { + token: user_token_address, + amount, + }], + creation_time: now, + refresh_time: now, + }, + router, + permission_account: permission_account_user_token_specified, + program: OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { + user_wallet_address, + platform_fee_bps: 0, + token_program_user: spl_token::id(), + token_program_searcher: spl_token::id(), + fee_token: fee_token.clone(), + referral_fee_bps, + user_mint_user_balance: 0, + token_account_initialization_config: TokenAccountInitializationConfigs { + user_ata_mint_user: TokenAccountInitializationConfig::UserPayer, + user_ata_mint_searcher: TokenAccountInitializationConfig::UserPayer, + ..TokenAccountInitializationConfigs::searcher_payer() + }, + memo: None, + }), + }; + + let opp_with_memo = OpportunitySvm { core_fields: OpportunityCoreFields:: { id: Uuid::new_v4(), permission_key: OpportunitySvm::get_permission_key( @@ -2167,12 +2206,9 @@ mod tests { fee_token, referral_fee_bps, user_mint_user_balance: 0, - token_account_initialization_config: TokenAccountInitializationConfigs { - user_ata_mint_user: TokenAccountInitializationConfig::UserPayer, - user_ata_mint_searcher: TokenAccountInitializationConfig::UserPayer, - ..TokenAccountInitializationConfigs::searcher_payer() - }, - memo: None, + token_account_initialization_config: + TokenAccountInitializationConfigs::searcher_payer(), + memo: Some("memo".to_string()), }), }; @@ -2183,6 +2219,7 @@ mod tests { opp_searcher_token_wsol.clone(), opp_indicative_price_taker.clone(), opp_with_user_payer.clone(), + opp_with_memo.clone(), ]; let opps_cloned = opps.clone(); @@ -2214,6 +2251,7 @@ mod tests { opp_searcher_token_wsol, opp_indicative_price_taker, opp_with_user_payer, + opp_with_memo, ], ) } @@ -5091,4 +5129,94 @@ mod tests { ) ); } + + #[tokio::test] + async fn test_verify_bid_with_memo_backward_compatibility() { + let (service, opportunities) = get_service(true); + + let opportunity = opportunities[6].clone(); // with memo + + let bid_amount = 1; + let searcher = Keypair::new(); + let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { + searcher: searcher.pubkey(), + opportunity_params: get_opportunity_params(opportunity.clone()), + bid_amount, + deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), + fee_receiver_relayer: Pubkey::new_unique(), + relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), + }) + .unwrap(); + get_verify_bid_result( + service, + searcher.insecure_clone(), + vec![instruction], + opportunity.clone(), + ) + .await + .unwrap(); + } + + async fn test_verify_bid_with_memo() { + let (service, opportunities) = get_service(true); + + let opportunity = opportunities[6].clone(); // with memo + + let bid_amount = 1; + let searcher = Keypair::new(); + let memo_instruction = svm::Svm::get_memo_instruction("memo".to_string()); + let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { + searcher: searcher.pubkey(), + opportunity_params: get_opportunity_params(opportunity.clone()), + bid_amount, + deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), + fee_receiver_relayer: Pubkey::new_unique(), + relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), + }) + .unwrap(); + get_verify_bid_result( + service, + searcher.insecure_clone(), + vec![memo_instruction, instruction], + opportunity.clone(), + ) + .await + .unwrap(); + } + + async fn test_verify_bid_with_invalid_memo() { + let (service, opportunities) = get_service(true); + + let opportunity = opportunities[6].clone(); // with memo + + let bid_amount = 1; + let searcher = Keypair::new(); + let memo_instruction = svm::Svm::get_memo_instruction("invalid memo".to_string()); // invalid memo + let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { + searcher: searcher.pubkey(), + opportunity_params: get_opportunity_params(opportunity.clone()), + bid_amount, + deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), + fee_receiver_relayer: Pubkey::new_unique(), + relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), + }) + .unwrap(); + let result = get_verify_bid_result( + service, + searcher, + vec![memo_instruction, instruction], + opportunity.clone(), + ) + .await; + assert_eq!( + result.unwrap_err(), + RestError::InvalidInstruction( + Some(0), + InstructionError::InvalidMemoString { + expected: "memo".to_string(), + found: "invalid memo".to_string(), + } + ) + ); + } } From 7ce927887db9e9b56294da8f20b987e95c2ae2d9 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 26 Mar 2025 19:54:10 +0000 Subject: [PATCH 11/14] add char limit --- auction-server/api-types/src/opportunity.rs | 8 ++++++++ auction-server/src/opportunity/api.rs | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index 66be24715..632b5f0bf 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -634,6 +634,14 @@ impl QuoteCreate { QuoteCreate::Svm(QuoteCreateSvm::V1(params)) => params.user_wallet_address, } } + + pub fn get_memo_length(&self) -> Option { + match self { + QuoteCreate::Svm(QuoteCreateSvm::V1(params)) => { + params.memo.as_ref().map(|memo| memo.len()) + } + } + } } #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] diff --git a/auction-server/src/opportunity/api.rs b/auction-server/src/opportunity/api.rs index a4c802bc2..7558b6316 100644 --- a/auction-server/src/opportunity/api.rs +++ b/auction-server/src/opportunity/api.rs @@ -202,6 +202,7 @@ pub async fn get_opportunities( } } +const MEMO_MAX_LENGTH: usize = 100; /// Submit a quote request. /// @@ -223,6 +224,16 @@ pub async fn post_quote( )); } } + + if let Some(length) = params.get_memo_length() { + if length > MEMO_MAX_LENGTH { + return Err(RestError::BadParameters(format!( + "Memo must be less than {} characters", + MEMO_MAX_LENGTH + ))); + } + } + let quote_create: QuoteCreateEntity = params.into(); let quote = store From 00377d1eca5c63bae322dbe4f9ea1b1fbc28b012 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 27 Mar 2025 14:34:10 +0000 Subject: [PATCH 12/14] fix: make backward comp change clearer --- auction-server/src/auction/service/verification.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 20e7c4961..fc2b88ced 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1165,7 +1165,7 @@ impl Service { ) -> Result<(), RestError> { let memo_instructions = Self::extract_program_instructions(tx, &spl_memo_client::ID); match (memo, memo_instructions.len()) { - (_, 0) => Ok(()), // todo: this is for backward compatibility, we can fix this once searchers have updated their sdk + (None, 0) => Ok(()), (Some(memo), 1) => { let (index, instruction) = memo_instructions[0]; // safe to index because we checked the length if instruction.data != memo.as_bytes() { @@ -1180,6 +1180,7 @@ impl Service { } Ok(()) } + (Some(_), 0) => Ok(()), // todo: this is for backward compatibility, we should remove this line once searchers have updated their sdk (_, _) => Err(RestError::InvalidInstruction( None, InstructionError::InvalidMemoInstructionCount { From e9961640b3403ef8de61d04ec965cd0ee091d434 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 27 Mar 2025 15:06:15 +0000 Subject: [PATCH 13/14] refactor test opportunities --- .../src/auction/service/verification.rs | 343 ++++++++---------- 1 file changed, 161 insertions(+), 182 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index fc2b88ced..4dc3a6881 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1867,9 +1867,19 @@ mod tests { } } + struct TestOpportunities { + pub user_token_specified: OpportunitySvm, + pub searcher_token_specified: OpportunitySvm, + pub user_token_wsol: OpportunitySvm, + pub searcher_token_wsol: OpportunitySvm, + pub with_indicative_price_taker: OpportunitySvm, + pub with_user_payer: OpportunitySvm, + pub with_memo: OpportunitySvm, + } + fn get_opportunity_service( chain_id: ChainId, - ) -> (MockService, Vec) { + ) -> (MockService, TestOpportunities) { let mut opportunity_service = MockService::::default(); let now = OffsetDateTime::now_utc(); let router = Pubkey::new_unique(); @@ -2101,7 +2111,7 @@ mod tests { referral_fee_bps, ); - let opp_indicative_price_taker = OpportunitySvm { + let opp_with_indicative_price_taker = OpportunitySvm { core_fields: OpportunityCoreFields:: { id: Uuid::new_v4(), permission_key: OpportunitySvm::get_permission_key( @@ -2218,7 +2228,7 @@ mod tests { opp_searcher_token_specified.clone(), opp_user_token_wsol.clone(), opp_searcher_token_wsol.clone(), - opp_indicative_price_taker.clone(), + opp_with_indicative_price_taker.clone(), opp_with_user_payer.clone(), opp_with_memo.clone(), ]; @@ -2245,19 +2255,19 @@ mod tests { ( opportunity_service, - vec![ - opp_user_token_specified, - opp_searcher_token_specified, - opp_user_token_wsol, - opp_searcher_token_wsol, - opp_indicative_price_taker, - opp_with_user_payer, - opp_with_memo, - ], + TestOpportunities { + user_token_specified: opp_user_token_specified, + searcher_token_specified: opp_searcher_token_specified, + user_token_wsol: opp_user_token_wsol, + searcher_token_wsol: opp_searcher_token_wsol, + with_indicative_price_taker: opp_with_indicative_price_taker, + with_user_payer: opp_with_user_payer, + with_memo: opp_with_memo, + }, ) } - fn get_service(mock_simulation: bool) -> (super::Service, Vec) { + fn get_service(mock_simulation: bool) -> (super::Service, TestOpportunities) { let chain_id = "solana".to_string(); let mut rpc_client = MockRpcClient::default(); if mock_simulation { @@ -2352,11 +2362,12 @@ mod tests { async fn test_verify_bid() { let (service, opportunities) = get_service(true); + let opportunity = opportunities.user_token_specified.clone(); let bid_amount = 1; let searcher = Keypair::new(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -2371,7 +2382,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -2380,7 +2391,7 @@ mod tests { .verify_bid(super::VerifyBidInput { bid_create }) .await .unwrap(); - let swap_params = get_opportunity_swap_params(opportunities[0].clone()); + let swap_params = get_opportunity_swap_params(opportunity.clone()); assert_eq!( result.0, BidChainDataSvm { @@ -2397,11 +2408,12 @@ mod tests { async fn test_verify_bid_with_relayer_fee_payer() { let (service, opportunities) = get_service(true); + let opportunity = opportunities.user_token_specified.clone(); let bid_amount = 1; let searcher = Keypair::new(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -2419,7 +2431,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -2439,11 +2451,12 @@ mod tests { async fn test_verify_bid_indicative_price_taker_skip_simulation() { let (service, opportunities) = get_service(false); // don't mock simulation + let opportunity = opportunities.with_indicative_price_taker.clone(); let bid_amount = 1; let searcher = Keypair::new(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[4].clone()), // use the indicative price taker opportunity + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -2458,7 +2471,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[4].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -2467,7 +2480,7 @@ mod tests { .verify_bid(super::VerifyBidInput { bid_create }) .await .unwrap(); - let swap_params = get_opportunity_swap_params(opportunities[4].clone()); + let swap_params = get_opportunity_swap_params(opportunity.clone()); assert_eq!( result.0, BidChainDataSvm { @@ -2489,7 +2502,7 @@ mod tests { service, searcher, vec![instruction.clone(), instruction], - opportunities[0].clone(), + opportunities.user_token_specified.clone(), ) .await; assert_eq!( @@ -2507,8 +2520,13 @@ mod tests { .repo .add_recent_prioritization_fee(minimum_budget) .await; - let result = - get_verify_bid_result(service, searcher, vec![], opportunities[0].clone()).await; + let result = get_verify_bid_result( + service, + searcher, + vec![], + opportunities.user_token_specified.clone(), + ) + .await; assert_eq!( result.unwrap_err(), RestError::SetComputeUnitPriceInstructionNotFound(minimum_budget), @@ -2530,7 +2548,7 @@ mod tests { service, searcher, vec![instruction], - opportunities[0].clone(), + opportunities.user_token_specified.clone(), ) .await; assert_eq!( @@ -2544,7 +2562,8 @@ mod tests { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); let mut instructions = Vec::new(); - let swap_params = get_opportunity_swap_params(opportunities[0].clone()); + let opportunity = opportunities.user_token_specified.clone(); + let swap_params = get_opportunity_swap_params(opportunity.clone()); for _ in 0..61 { // Adjust number to exceed limit let transfer_instruction = system_instruction::transfer( @@ -2555,7 +2574,7 @@ mod tests { instructions.push(transfer_instruction); } let result = - get_verify_bid_result(service, searcher, instructions, opportunities[0].clone()).await; + get_verify_bid_result(service, searcher, instructions, opportunity.clone()).await; assert_eq!( result.unwrap_err(), RestError::TransactionSizeTooLarge(1235, PACKET_DATA_SIZE) @@ -2566,7 +2585,7 @@ mod tests { async fn test_verify_bid_when_opportunity_not_found() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); opportunity.core_fields.id = Uuid::new_v4(); let result = get_verify_bid_result(service, searcher, vec![], opportunity).await; assert_eq!(result.unwrap_err(), RestError::SwapOpportunityNotFound); @@ -2602,7 +2621,7 @@ mod tests { service.clone(), searcher, vec![instruction], - opportunities[0].clone(), + opportunities.user_token_specified.clone(), ) .await; assert_eq!( @@ -2717,7 +2736,7 @@ mod tests { service.clone(), searcher, vec![instruction], - opportunities[0].clone(), + opportunities.user_token_specified.clone(), ) .await; assert_eq!( @@ -2751,7 +2770,7 @@ mod tests { service.clone(), searcher, vec![instruction], - opportunities[0].clone(), + opportunities.user_token_specified.clone(), ) .await; assert_eq!( @@ -2775,7 +2794,7 @@ mod tests { service.clone(), searcher, vec![instruction], - opportunities[0].clone(), + opportunities.user_token_specified.clone(), ) .await; assert_eq!( @@ -2792,9 +2811,10 @@ mod tests { async fn test_verify_bid_when_multiple_express_relay_instructions() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -2819,7 +2839,7 @@ mod tests { service, searcher, vec![swap_instruction, submit_bid_instruction], - opportunities[0].clone(), + opportunity.clone(), ) .await; assert_eq!( @@ -2832,8 +2852,13 @@ mod tests { async fn test_verify_bid_when_no_express_relay_instructions() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let result = - get_verify_bid_result(service, searcher, vec![], opportunities[0].clone()).await; + let result = get_verify_bid_result( + service, + searcher, + vec![], + opportunities.user_token_specified.clone(), + ) + .await; assert_eq!( result.unwrap_err(), RestError::InvalidExpressRelayInstructionCount(0), @@ -2844,7 +2869,7 @@ mod tests { async fn test_verify_bid_when_invalid_user_wallet_address() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let mut program = match opportunity.program { OpportunitySvmProgram::Swap(program) => program, _ => panic!("Expected swap program"), @@ -2855,7 +2880,7 @@ mod tests { opportunity.program = OpportunitySvmProgram::Swap(program); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -2863,13 +2888,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::UserWalletAddress { @@ -2883,13 +2903,13 @@ mod tests { async fn test_verify_bid_when_invalid_mint_searcher() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let expected = opportunity.core_fields.sell_tokens[0].token; opportunity.core_fields.sell_tokens[0].token = Pubkey::new_unique(); let found = opportunity.core_fields.sell_tokens[0].token; let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -2897,13 +2917,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::MintSearcher { @@ -2917,13 +2932,13 @@ mod tests { async fn test_verify_bid_when_invalid_mint_user() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let expected = opportunity.core_fields.buy_tokens[0].token; opportunity.core_fields.buy_tokens[0].token = Pubkey::new_unique(); let found = opportunity.core_fields.buy_tokens[0].token; let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -2931,13 +2946,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::MintUser { expected, found }) @@ -2948,7 +2958,7 @@ mod tests { async fn test_verify_bid_when_invalid_token_program_searcher() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let mut program = match opportunity.program { OpportunitySvmProgram::Swap(program) => program, _ => panic!("Expected swap program"), @@ -2959,7 +2969,7 @@ mod tests { opportunity.program = OpportunitySvmProgram::Swap(program); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -2967,13 +2977,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::TokenProgramSearcher { @@ -2987,7 +2992,7 @@ mod tests { async fn test_verify_bid_when_invalid_token_program_user() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let mut program = match opportunity.program { OpportunitySvmProgram::Swap(program) => program, _ => panic!("Expected swap program"), @@ -2998,7 +3003,7 @@ mod tests { opportunity.program = OpportunitySvmProgram::Swap(program); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3006,13 +3011,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::TokenProgramUser { @@ -3026,13 +3026,13 @@ mod tests { async fn test_verify_bid_when_invalid_token_amount_searcher() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[1].clone(); + let mut opportunity = opportunities.searcher_token_specified.clone(); let mut token = opportunity.core_fields.sell_tokens[0].clone(); token.amount += 1; opportunity.core_fields.sell_tokens[0] = token.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3040,13 +3040,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[1].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::AmountSearcher { @@ -3060,13 +3055,13 @@ mod tests { async fn test_verify_bid_when_invalid_token_amount_user() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let mut token = opportunity.core_fields.buy_tokens[0].clone(); token.amount += 1; opportunity.core_fields.buy_tokens[0] = token.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3074,13 +3069,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::AmountUser { @@ -3094,7 +3084,7 @@ mod tests { async fn test_verify_bid_when_invalid_fee_token() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let mut program = match opportunity.program { OpportunitySvmProgram::Swap(program) => program, _ => panic!("Expected swap program"), @@ -3103,7 +3093,7 @@ mod tests { opportunity.program = OpportunitySvmProgram::Swap(program); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3111,13 +3101,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::FeeToken { @@ -3131,7 +3116,7 @@ mod tests { async fn test_verify_bid_when_invalid_referral_fee_bps() { let (service, opportunities) = get_service(true); let searcher = Keypair::new(); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let mut program = match opportunity.program { OpportunitySvmProgram::Swap(program) => program, _ => panic!("Expected swap program"), @@ -3140,7 +3125,7 @@ mod tests { opportunity.program = OpportunitySvmProgram::Swap(program.clone()); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunity), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3148,13 +3133,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::ReferralFee { @@ -3168,9 +3148,10 @@ mod tests { async fn test_verify_bid_when_no_transfer_instruction() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[2].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3178,13 +3159,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[2].clone(), // User token wsol - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidInstruction(None, InstructionError::InvalidTransferInstructionsCount) @@ -3195,9 +3171,10 @@ mod tests { async fn test_verify_bid_when_no_transfer_instruction_is_allowed() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3211,7 +3188,7 @@ mod tests { service, searcher, vec![swap_instruction, transfer_instruction], - opportunities[0].clone(), + opportunity, ) .await; assert_eq!( @@ -3224,9 +3201,10 @@ mod tests { async fn test_verify_bid_when_no_close_account_instruction_is_allowed() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount: 1, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)) .unix_timestamp(), @@ -3248,7 +3226,7 @@ mod tests { service, searcher, vec![swap_instruction, close_account_instruction], - opportunities[0].clone(), + opportunity, ) .await; assert_eq!( @@ -3264,7 +3242,7 @@ mod tests { async fn test_verify_bid_when_multiple_transfer_instructions() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3301,7 +3279,7 @@ mod tests { async fn test_verify_bid_when_invalid_from_account_transfer_instruction() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3339,7 +3317,7 @@ mod tests { async fn test_verify_bid_when_invalid_to_account_transfer_instruction() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3381,7 +3359,7 @@ mod tests { async fn test_verify_bid_when_invalid_amount_transfer_instruction() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3426,7 +3404,7 @@ mod tests { async fn test_verify_bid_when_multiple_sync_native_instructions() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3481,7 +3459,7 @@ mod tests { async fn test_verify_bid_when_no_sync_native_instructions() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3527,7 +3505,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_user_wsol() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -3597,7 +3575,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_user_wsol_with_close_account_instruction() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -3640,7 +3618,7 @@ mod tests { transfer_instruction, sync_native_instruction, swap_instruction, - close_account_instruction, // <--- this is the only difference from the previous test + close_account_instruction, // <--- this is the only difference from test_verify_bid_user_wsol ], Some(&searcher.pubkey()), ); @@ -3676,7 +3654,7 @@ mod tests { async fn test_verify_bid_when_multiple_close_account_instructions() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3728,7 +3706,7 @@ mod tests { async fn test_verify_bid_when_no_close_account_instructions() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3754,7 +3732,7 @@ mod tests { async fn test_verify_bid_when_invalid_account_to_close() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3798,7 +3776,7 @@ mod tests { async fn test_verify_bid_when_invalid_close_account_destination() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -3848,7 +3826,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_searcher_wsol() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -3911,7 +3889,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_invalid_associated_router_token_account() { let (service, opportunities) = get_service(true); - let mut opportunity = opportunities[0].clone(); + let mut opportunity = opportunities.user_token_specified.clone(); let expected = get_associated_token_address( &opportunity.router, &opportunity.core_fields.buy_tokens[0].token, @@ -3932,13 +3910,8 @@ mod tests { relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let result = get_verify_bid_result( - service, - searcher, - vec![swap_instruction], - opportunities[0].clone(), - ) - .await; + let result = + get_verify_bid_result(service, searcher, vec![swap_instruction], opportunity).await; assert_eq!( result.unwrap_err(), RestError::InvalidSwapInstruction(SwapInstructionError::AssociatedRouterTokenAccount { @@ -3954,12 +3927,13 @@ mod tests { let bid_amount = 1; let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let deadline = (OffsetDateTime::now_utc() + BID_MINIMUM_LIFE_TIME_SVM_OTHER - Duration::seconds(1)) .unix_timestamp(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline, fee_receiver_relayer: Pubkey::new_unique(), @@ -3974,7 +3948,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -3997,9 +3971,10 @@ mod tests { let bid_amount = 1; let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -4013,7 +3988,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -4033,9 +4008,10 @@ mod tests { let bid_amount = 1; let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let mut instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -4060,7 +4036,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -4080,16 +4056,17 @@ mod tests { let bid_amount = 1; let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(), }) .unwrap(); - let program = match opportunities[0].program.clone() { + let program = match opportunity.program.clone() { OpportunitySvmProgram::Swap(program) => program, _ => panic!("Expected swap program"), }; @@ -4102,7 +4079,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -4141,9 +4118,10 @@ mod tests { let bid_amount = 1; let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -4158,7 +4136,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -4185,9 +4163,10 @@ mod tests { let bid_amount = 1; let searcher = Keypair::new(); + let opportunity = opportunities.user_token_specified.clone(); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), - opportunity_params: get_opportunity_params(opportunities[0].clone()), + opportunity_params: get_opportunity_params(opportunity.clone()), bid_amount, deadline: (OffsetDateTime::now_utc() + Duration::minutes(1)).unix_timestamp(), fee_receiver_relayer: Pubkey::new_unique(), @@ -4202,7 +4181,7 @@ mod tests { initiation_time: OffsetDateTime::now_utc(), profile: None, chain_data: BidChainDataCreateSvm::Swap(BidChainDataSwapCreateSvm { - opportunity_id: opportunities[0].core_fields.id, + opportunity_id: opportunity.core_fields.id, transaction: transaction.clone().into(), }), }; @@ -4227,7 +4206,7 @@ mod tests { async fn test_verify_bid_when_invalid_close_account_owner() { let (service, opportunities) = get_service(false); let searcher = Keypair::new(); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()), @@ -4277,7 +4256,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_user_wsol_searcher_unwrap() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4355,7 +4334,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_invalid_searcher_account_to_close() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[2].clone(); // User token wsol + let opportunity = opportunities.user_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4419,7 +4398,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_searcher_wsol_searcher_wrap() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4496,7 +4475,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_invalid_searcher_account_from_transfer() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4556,7 +4535,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_invalid_searcher_account_to_transfer() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[3].clone(); // Searcher token wsol + let opportunity = opportunities.searcher_token_wsol.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4618,7 +4597,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_missing_user_mint_ata_creation() { let (service, opportunities) = get_service(false); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4658,7 +4637,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_missing_searcher_mint_ata_creation() { let (service, opportunities) = get_service(false); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4706,7 +4685,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4756,7 +4735,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_backward_compatible() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4806,7 +4785,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_invalid_payer() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4867,7 +4846,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_invalid_mint() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -4934,7 +4913,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_invalid_owner() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -5000,7 +4979,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_invalid_token_program() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -5065,7 +5044,7 @@ mod tests { #[tokio::test] async fn test_verify_bid_when_user_payer_invalid_payer_extra_account_creation() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[5].clone(); // User payer + let opportunity = opportunities.with_user_payer.clone(); let bid_amount = 1; let searcher = Keypair::new(); let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { @@ -5135,7 +5114,7 @@ mod tests { async fn test_verify_bid_with_memo_backward_compatibility() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[6].clone(); // with memo + let opportunity = opportunities.with_memo.clone(); let bid_amount = 1; let searcher = Keypair::new(); @@ -5161,7 +5140,7 @@ mod tests { async fn test_verify_bid_with_memo() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[6].clone(); // with memo + let opportunity = opportunities.with_memo.clone(); let bid_amount = 1; let searcher = Keypair::new(); @@ -5188,7 +5167,7 @@ mod tests { async fn test_verify_bid_with_invalid_memo() { let (service, opportunities) = get_service(true); - let opportunity = opportunities[6].clone(); // with memo + let opportunity = opportunities.with_memo.clone(); let bid_amount = 1; let searcher = Keypair::new(); From b2dd9d0c3331b93c51a9da185817dee7b01d8a65 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 27 Mar 2025 18:29:41 +0000 Subject: [PATCH 14/14] Fixed namings. --- auction-server/src/auction/service/verification.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 4dc3a6881..bc2d0d427 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -5111,7 +5111,7 @@ mod tests { } #[tokio::test] - async fn test_verify_bid_with_memo_backward_compatibility() { + async fn test_verify_bid_with_missing_memo() { let (service, opportunities) = get_service(true); let opportunity = opportunities.with_memo.clone(); @@ -5164,14 +5164,14 @@ mod tests { .unwrap(); } - async fn test_verify_bid_with_invalid_memo() { + async fn test_verify_bid_with_mismatched_memo() { let (service, opportunities) = get_service(true); let opportunity = opportunities.with_memo.clone(); let bid_amount = 1; let searcher = Keypair::new(); - let memo_instruction = svm::Svm::get_memo_instruction("invalid memo".to_string()); // invalid memo + let memo_instruction = svm::Svm::get_memo_instruction("mismatched memo".to_string()); let instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams { searcher: searcher.pubkey(), opportunity_params: get_opportunity_params(opportunity.clone()),