From 860b48c03118274c66edd4478316c9e859ec10df Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Tue, 30 Jun 2026 12:06:02 -0300 Subject: [PATCH] refactor(amm)!: select swap direction by input holding, sign only the input Replace the `token_definition_id_in` argument with a role-based account interface: swaps now take a `user_input_holding` and a `user_output_holding` instead of positional token-A/token-B holdings. Direction is derived from the input holding's own token definition, and the input slot is a framework-level `#[account(signer)]` so authorization is enforced before execution rather than delegated solely to the downstream token transfer. The output holding only receives and needs no signature. This removes the ambiguity the previous arg-based model carried (the arg could disagree with the signed holding) and makes the IDL express the signing rule: `user_input_holding` is `signer: true`, `user_output_holding` is `signer: false`. The two user-holding post-states are echoed in the guest's declared slot order (input, then output); the framework matches post-states to accounts by position, so the internal A/B mapping used for reserve bookkeeping must not leak into the returned order. BREAKING CHANGE: The AMM swap instruction interface changed and the guest ImageID/ProgramId changes as a result. --- artifacts/amm-idl.json | 20 +- programs/amm/core/src/lib.rs | 25 +- programs/amm/methods/guest/src/bin/amm.rs | 30 +- programs/amm/src/swap.rs | 74 ++- programs/amm/src/tests.rs | 46 +- programs/integration_tests/tests/amm.rs | 541 ++++++++++++++++++++-- 6 files changed, 597 insertions(+), 139 deletions(-) diff --git a/artifacts/amm-idl.json b/artifacts/amm-idl.json index dbe1260..d191404 100644 --- a/artifacts/amm-idl.json +++ b/artifacts/amm-idl.json @@ -423,15 +423,15 @@ "init": false }, { - "name": "user_holding_a", + "name": "user_input_holding", "writable": true, "signer": true, "init": false }, { - "name": "user_holding_b", + "name": "user_output_holding", "writable": true, - "signer": true, + "signer": false, "init": false }, { @@ -456,10 +456,6 @@ "name": "min_amount_out", "type": "u128" }, - { - "name": "token_definition_id_in", - "type": "account_id" - }, { "name": "deadline", "type": "u64" @@ -494,15 +490,15 @@ "init": false }, { - "name": "user_holding_a", + "name": "user_input_holding", "writable": true, "signer": true, "init": false }, { - "name": "user_holding_b", + "name": "user_output_holding", "writable": true, - "signer": true, + "signer": false, "init": false }, { @@ -527,10 +523,6 @@ "name": "max_amount_in", "type": "u128" }, - { - "name": "token_definition_id_in", - "type": "account_id" - }, { "name": "deadline", "type": "u64" diff --git a/programs/amm/core/src/lib.rs b/programs/amm/core/src/lib.rs index feaa063..4ca2765 100644 --- a/programs/amm/core/src/lib.rs +++ b/programs/amm/core/src/lib.rs @@ -172,15 +172,18 @@ pub enum Instruction { deadline: u64, }, - /// Swap some quantity of Tokens (either Token A or Token B) - /// while maintaining the Pool constant product. + /// Swap some quantity of tokens while maintaining the Pool constant product. + /// + /// Swap direction is determined by the input holding: `user_input_holding`'s token definition + /// selects which pool token is sold. That holding must be signed so the downstream token + /// transfer can debit it; `user_output_holding` only receives and needs no signature. /// /// Required accounts: /// - AMM Pool (initialized) /// - Vault Holding Account for Token A (initialized) /// - Vault Holding Account for Token B (initialized) - /// - User Holding Account for Token A - /// - User Holding Account for Token B; either is authorized. + /// - User Input Holding Account (initialized, signed) — the token being sold + /// - User Output Holding Account (initialized) — receives the token being bought /// - Current Tick Account, the pool's TWAP PDA derived as /// `compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id)`; refreshed /// with the new spot price @@ -188,20 +191,23 @@ pub enum Instruction { SwapExactInput { swap_amount_in: u128, min_amount_out: u128, - token_definition_id_in: AccountId, /// Unix timestamp (milliseconds) after which this transaction is invalid. deadline: u64, }, - /// Swap tokens specifying the exact desired output amount, - /// while maintaining the Pool constant product. + /// Swap tokens specifying the exact desired output amount while maintaining the Pool constant + /// product. + /// + /// Swap direction is determined by the input holding: `user_input_holding`'s token definition + /// selects which pool token is sold. That holding must be signed so the downstream token + /// transfer can debit it; `user_output_holding` only receives and needs no signature. /// /// Required accounts: /// - AMM Pool (initialized) /// - Vault Holding Account for Token A (initialized) /// - Vault Holding Account for Token B (initialized) - /// - User Holding Account for Token A - /// - User Holding Account for Token B; either is authorized. + /// - User Input Holding Account (initialized, signed) — the token being sold + /// - User Output Holding Account (initialized) — receives the token being bought /// - Current Tick Account, the pool's TWAP PDA derived as /// `compute_current_tick_account_pda(twap_oracle_program_id, pool.account_id)`; refreshed /// with the new spot price @@ -209,7 +215,6 @@ pub enum Instruction { SwapExactOutput { exact_amount_out: u128, max_amount_in: u128, - token_definition_id_in: AccountId, /// Unix timestamp (milliseconds) after which this transaction is invalid. deadline: u64, }, diff --git a/programs/amm/methods/guest/src/bin/amm.rs b/programs/amm/methods/guest/src/bin/amm.rs index aa33b3b..a05db60 100644 --- a/programs/amm/methods/guest/src/bin/amm.rs +++ b/programs/amm/methods/guest/src/bin/amm.rs @@ -298,6 +298,9 @@ mod amm { } /// Swap some quantity of tokens while maintaining the pool constant product. + /// + /// The swap direction is the input holding's own token; `user_input_holding` must be signed so + /// the downstream token transfer can debit it. `user_output_holding` only receives. #[expect( clippy::too_many_arguments, reason = "instruction interface requires explicit pool, vault, user accounts, and bounds" @@ -313,15 +316,14 @@ mod amm { #[account(mut)] vault_b: AccountWithMetadata, #[account(mut, signer)] - user_holding_a: AccountWithMetadata, - #[account(mut, signer)] - user_holding_b: AccountWithMetadata, + user_input_holding: AccountWithMetadata, + #[account(mut)] + user_output_holding: AccountWithMetadata, #[account(mut)] current_tick_account: AccountWithMetadata, clock: AccountWithMetadata, swap_amount_in: u128, min_amount_out: u128, - token_definition_id_in: AccountId, deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::swap::swap_exact_input( @@ -329,13 +331,12 @@ mod amm { pool, vault_a, vault_b, - user_holding_a, - user_holding_b, + user_input_holding, + user_output_holding, current_tick_account, clock, swap_amount_in, min_amount_out, - token_definition_id_in, ctx.self_program_id, ); Ok(spel_framework::SpelOutput::execute(post_states, chained_calls) @@ -343,6 +344,9 @@ mod amm { } /// Swap tokens specifying the exact desired output amount. + /// + /// The swap direction is the input holding's own token; `user_input_holding` must be signed so + /// the downstream token transfer can debit it. `user_output_holding` only receives. #[expect( clippy::too_many_arguments, reason = "instruction interface requires explicit pool, vault, user accounts, and bounds" @@ -358,15 +362,14 @@ mod amm { #[account(mut)] vault_b: AccountWithMetadata, #[account(mut, signer)] - user_holding_a: AccountWithMetadata, - #[account(mut, signer)] - user_holding_b: AccountWithMetadata, + user_input_holding: AccountWithMetadata, + #[account(mut)] + user_output_holding: AccountWithMetadata, #[account(mut)] current_tick_account: AccountWithMetadata, clock: AccountWithMetadata, exact_amount_out: u128, max_amount_in: u128, - token_definition_id_in: AccountId, deadline: u64, ) -> SpelResult { let (post_states, chained_calls) = amm_program::swap::swap_exact_output( @@ -374,13 +377,12 @@ mod amm { pool, vault_a, vault_b, - user_holding_a, - user_holding_b, + user_input_holding, + user_output_holding, current_tick_account, clock, exact_amount_out, max_amount_in, - token_definition_id_in, ctx.self_program_id, ); Ok(spel_framework::SpelOutput::execute(post_states, chained_calls) diff --git a/programs/amm/src/swap.rs b/programs/amm/src/swap.rs index d6a1d2e..1f10252 100644 --- a/programs/amm/src/swap.rs +++ b/programs/amm/src/swap.rs @@ -65,8 +65,10 @@ fn finalize_swap( pool_def_data: PoolDefinition, vault_a: AccountWithMetadata, vault_b: AccountWithMetadata, - user_holding_a: AccountWithMetadata, - user_holding_b: AccountWithMetadata, + // Echoed back at the input/output slot positions the guest declared, so the framework matches + // each post-state to the correct account regardless of swap direction. + user_holding_input: AccountWithMetadata, + user_holding_output: AccountWithMetadata, current_tick_account: AccountWithMetadata, clock: AccountWithMetadata, deposit_a: u128, @@ -124,8 +126,8 @@ fn finalize_swap( AccountPostState::new(pool_post), AccountPostState::new(vault_a.account), AccountPostState::new(vault_b.account), - AccountPostState::new(user_holding_a.account), - AccountPostState::new(user_holding_b.account), + AccountPostState::new(user_holding_input.account), + AccountPostState::new(user_holding_output.account), AccountPostState::new(current_tick_account.account), AccountPostState::new(clock.account), ]; @@ -143,13 +145,12 @@ pub fn swap_exact_input( pool: AccountWithMetadata, vault_a: AccountWithMetadata, vault_b: AccountWithMetadata, - user_holding_a: AccountWithMetadata, - user_holding_b: AccountWithMetadata, + user_input_holding: AccountWithMetadata, + user_output_holding: AccountWithMetadata, current_tick_account: AccountWithMetadata, clock: AccountWithMetadata, swap_amount_in: u128, min_amount_out: u128, - token_in_id: AccountId, amm_program_id: ProgramId, ) -> (Vec, Vec) { let pool_def_data = validate_swap_setup(&pool, &vault_a, &vault_b); @@ -173,6 +174,20 @@ pub fn swap_exact_input( vault_b.account.program_owner, token_program_id, "Vault B must be owned by the configured Token Program" ); + + // Swap direction is taken from the (signed) input holding's own token definition, then the + // role-based holdings are mapped back to the pool's stored A/B order so the rest of the + // routine — reserve bookkeeping and finalize — stays keyed to token A/B. + let token_in_id = token_core::TokenHolding::try_from(&user_input_holding.account.data) + .expect("Swap exact input: input holding must be a valid token holding") + .definition_id(); + let (user_holding_a, user_holding_b) = if token_in_id == pool_def_data.definition_token_a_id { + (user_input_holding, user_output_holding) + } else if token_in_id == pool_def_data.definition_token_b_id { + (user_output_holding, user_input_holding) + } else { + panic!("Swap exact input: input holding token is not part of the pool"); + }; assert_eq!( user_holding_a.account.program_owner, token_program_id, "User Token A holding must be owned by the configured Token Program" @@ -228,14 +243,23 @@ pub fn swap_exact_input( panic!("AccountId is not a token type for the pool"); }; + // Echo the two user holdings in the guest's declared slot order (input, then output) so the + // framework matches each post-state to the right account. The a/b mapping above only drives the + // reserve/vault bookkeeping; post-states are matched to accounts positionally. + let (user_holding_input, user_holding_output) = + if token_in_id == pool_def_data.definition_token_a_id { + (user_holding_a, user_holding_b) + } else { + (user_holding_b, user_holding_a) + }; let (post_states, update_tick_call) = finalize_swap( config, pool, pool_def_data, vault_a, vault_b, - user_holding_a, - user_holding_b, + user_holding_input, + user_holding_output, current_tick_account, clock, deposit_a, @@ -343,13 +367,12 @@ pub fn swap_exact_output( pool: AccountWithMetadata, vault_a: AccountWithMetadata, vault_b: AccountWithMetadata, - user_holding_a: AccountWithMetadata, - user_holding_b: AccountWithMetadata, + user_input_holding: AccountWithMetadata, + user_output_holding: AccountWithMetadata, current_tick_account: AccountWithMetadata, clock: AccountWithMetadata, exact_amount_out: u128, max_amount_in: u128, - token_in_id: AccountId, amm_program_id: ProgramId, ) -> (Vec, Vec) { let pool_def_data = validate_swap_setup(&pool, &vault_a, &vault_b); @@ -373,6 +396,20 @@ pub fn swap_exact_output( vault_b.account.program_owner, token_program_id, "Vault B must be owned by the configured Token Program" ); + + // Swap direction is taken from the (signed) input holding's own token definition, then the + // role-based holdings are mapped back to the pool's stored A/B order so the rest of the + // routine — reserve bookkeeping and finalize — stays keyed to token A/B. + let token_in_id = token_core::TokenHolding::try_from(&user_input_holding.account.data) + .expect("Swap exact output: input holding must be a valid token holding") + .definition_id(); + let (user_holding_a, user_holding_b) = if token_in_id == pool_def_data.definition_token_a_id { + (user_input_holding, user_output_holding) + } else if token_in_id == pool_def_data.definition_token_b_id { + (user_output_holding, user_input_holding) + } else { + panic!("Swap exact output: input holding token is not part of the pool"); + }; assert_eq!( user_holding_a.account.program_owner, token_program_id, "User Token A holding must be owned by the configured Token Program" @@ -428,14 +465,23 @@ pub fn swap_exact_output( panic!("AccountId is not a token type for the pool"); }; + // Echo the two user holdings in the guest's declared slot order (input, then output) so the + // framework matches each post-state to the right account. The a/b mapping above only drives the + // reserve/vault bookkeeping; post-states are matched to accounts positionally. + let (user_holding_input, user_holding_output) = + if token_in_id == pool_def_data.definition_token_a_id { + (user_holding_a, user_holding_b) + } else { + (user_holding_b, user_holding_a) + }; let (post_states, update_tick_call) = finalize_swap( config, pool, pool_def_data, vault_a, vault_b, - user_holding_a, - user_holding_b, + user_holding_input, + user_holding_output, current_tick_account, clock, deposit_a, diff --git a/programs/amm/src/tests.rs b/programs/amm/src/tests.rs index bb37c7c..5852983 100644 --- a/programs/amm/src/tests.rs +++ b/programs/amm/src/tests.rs @@ -2424,7 +2424,7 @@ fn test_call_new_definition_chained_call_successful() { assert_eq!(post_states.len(), 11); } -#[should_panic(expected = "AccountId is not a token type for the pool")] +#[should_panic(expected = "Swap exact input: input holding token is not part of the pool")] #[test] fn test_call_swap_incorrect_token_type() { let _post_states = swap_exact_input( @@ -2432,13 +2432,12 @@ fn test_call_swap_incorrect_token_type() { AccountWithMetadataForTests::pool_definition_init(), AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_b_init(), - AccountWithMetadataForTests::user_holding_a(), + AccountWithMetadataForTests::user_holding_lp_uninit(), AccountWithMetadataForTests::user_holding_b(), AccountWithMetadataForTests::current_tick_account_uninit(), AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_lp_definition_id(), AMM_PROGRAM_ID, ); } @@ -2457,7 +2456,6 @@ fn test_call_swap_vault_a_omitted() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2476,7 +2474,6 @@ fn test_call_swap_vault_b_omitted() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2495,7 +2492,6 @@ fn test_call_swap_reserves_vault_mismatch_1() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2514,7 +2510,6 @@ fn test_call_swap_reserves_vault_mismatch_2() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2533,7 +2528,6 @@ fn test_call_swap_below_minimum_liquidity() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2557,7 +2551,6 @@ fn test_call_swap_rejects_unsupported_fee_tier() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_a_low(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2576,7 +2569,6 @@ fn test_call_swap_below_min_out() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out_too_high(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2595,7 +2587,6 @@ fn test_call_swap_effective_amount_zero() { AccountWithMetadataForTests::clock(), 1, 0, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2614,7 +2605,6 @@ fn test_call_swap_output_rounds_to_zero() { AccountWithMetadataForTests::clock(), 2, 0, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2633,7 +2623,6 @@ fn test_call_swap_exact_input_rejects_amount_that_rounds_down_below_target_outpu AccountWithMetadataForTests::clock(), 2, 1, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2651,7 +2640,6 @@ fn test_call_swap_exact_input_accepts_smallest_amount_for_rounded_boundary() { AccountWithMetadataForTests::clock(), 3, 1, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); @@ -2721,7 +2709,6 @@ fn test_call_swap_chained_call_successful_1() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_a_low(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); @@ -2754,13 +2741,12 @@ fn test_call_swap_chained_call_successful_2() { AccountWithMetadataForTests::pool_definition_init(), AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_b_init(), - AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_b(), + AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::current_tick_account_uninit(), AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_b(), BalanceForTests::min_amount_out(), - IdForTests::token_b_definition_id(), AMM_PROGRAM_ID, ); @@ -2786,7 +2772,7 @@ fn test_call_swap_chained_call_successful_2() { assert_update_tick_call(&chained_calls, pool_post.account()); } -#[should_panic(expected = "AccountId is not a token type for the pool")] +#[should_panic(expected = "Swap exact output: input holding token is not part of the pool")] #[test] fn call_swap_exact_output_incorrect_token_type() { let _post_states = swap_exact_output( @@ -2794,13 +2780,12 @@ fn call_swap_exact_output_incorrect_token_type() { AccountWithMetadataForTests::pool_definition_init(), AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_b_init(), - AccountWithMetadataForTests::user_holding_a(), + AccountWithMetadataForTests::user_holding_lp_uninit(), AccountWithMetadataForTests::user_holding_b(), AccountWithMetadataForTests::current_tick_account_uninit(), AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::max_amount_in(), - IdForTests::token_lp_definition_id(), AMM_PROGRAM_ID, ); } @@ -2819,7 +2804,6 @@ fn call_swap_exact_output_vault_a_omitted() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2838,7 +2822,6 @@ fn call_swap_exact_output_vault_b_omitted() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2857,7 +2840,6 @@ fn call_swap_exact_output_reserves_vault_mismatch_1() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2876,7 +2858,6 @@ fn call_swap_exact_output_reserves_vault_mismatch_2() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2895,7 +2876,6 @@ fn call_swap_exact_output_below_minimum_liquidity() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2914,7 +2894,6 @@ fn call_swap_exact_output_exceeds_max_in() { AccountWithMetadataForTests::clock(), 166_u128, 100_u128, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2933,7 +2912,6 @@ fn call_swap_exact_output_zero() { AccountWithMetadataForTests::clock(), 0_u128, 500_u128, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2952,7 +2930,6 @@ fn call_swap_exact_output_exceeds_reserve() { AccountWithMetadataForTests::clock(), BalanceForTests::vault_b_reserve_init(), BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -2970,7 +2947,6 @@ fn call_swap_exact_output_chained_call_successful() { AccountWithMetadataForTests::clock(), BalanceForTests::max_amount_in(), BalanceForTests::vault_b_reserve_init(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); @@ -3001,13 +2977,12 @@ fn call_swap_exact_output_chained_call_successful_2() { AccountWithMetadataForTests::pool_definition_swap_exact_output_init(), AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_b_init(), - AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_b(), + AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::current_tick_account_uninit(), AccountWithMetadataForTests::clock(), 285, 300, - IdForTests::token_b_definition_id(), AMM_PROGRAM_ID, ); @@ -3047,7 +3022,6 @@ fn call_swap_exact_output_fee_enforced() { AccountWithMetadataForTests::clock(), 166_u128, // exact_amount_out: token_b 499_u128, // max_amount_in: still one short after fee rounding - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -3069,7 +3043,6 @@ fn call_swap_exact_output_rejects_max_in_that_rounds_down_below_target_output() AccountWithMetadataForTests::clock(), 1, 2, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -3087,7 +3060,6 @@ fn call_swap_exact_output_accepts_smallest_max_in_for_rounded_boundary() { AccountWithMetadataForTests::clock(), 1, 3, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); @@ -3185,7 +3157,6 @@ fn swap_exact_output_overflow_protection() { 2, // exact_amount_out: small, valid (< reserve_b) 1, // max_amount_in: tiny — real deposit would be enormous, but // overflow wraps it to 0, making 0 <= 1 pass silently - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -3878,7 +3849,6 @@ fn swap_exact_input_overflow_protection() { AccountWithMetadataForTests::clock(), 3, 1, - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); @@ -4076,7 +4046,6 @@ fn test_swap_exact_input_rejects_user_holding_a_wrong_program() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -4095,7 +4064,6 @@ fn test_swap_exact_input_rejects_user_holding_b_wrong_program() { AccountWithMetadataForTests::clock(), BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -4114,7 +4082,6 @@ fn test_swap_exact_output_rejects_user_holding_a_wrong_program() { AccountWithMetadataForTests::clock(), 166, BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } @@ -4133,7 +4100,6 @@ fn test_swap_exact_output_rejects_user_holding_b_wrong_program() { AccountWithMetadataForTests::clock(), 166, BalanceForTests::max_amount_in(), - IdForTests::token_a_definition_id(), AMM_PROGRAM_ID, ); } diff --git a/programs/integration_tests/tests/amm.rs b/programs/integration_tests/tests/amm.rs index af711aa..6d6896a 100644 --- a/programs/integration_tests/tests/amm.rs +++ b/programs/integration_tests/tests/amm.rs @@ -257,6 +257,46 @@ impl Balances { 10_415 } + fn exact_output_a_to_b_amount_in() -> u128 { + 437 + } + + fn reserve_a_swap_exact_output_a_to_b() -> u128 { + Self::vault_a_init() + Self::exact_output_a_to_b_amount_in() + } + + fn reserve_b_swap_exact_output_a_to_b() -> u128 { + Self::vault_b_init() - Self::swap_min_out() + } + + fn user_a_swap_exact_output_a_to_b() -> u128 { + Self::user_a_init() - Self::exact_output_a_to_b_amount_in() + } + + fn user_b_swap_exact_output_a_to_b() -> u128 { + Self::user_b_init() + Self::swap_min_out() + } + + fn exact_output_b_to_a_amount_in() -> u128 { + 106 + } + + fn reserve_a_swap_exact_output_b_to_a() -> u128 { + Self::vault_a_init() - Self::swap_min_out() + } + + fn reserve_b_swap_exact_output_b_to_a() -> u128 { + Self::vault_b_init() + Self::exact_output_b_to_a_amount_in() + } + + fn user_a_swap_exact_output_b_to_a() -> u128 { + Self::user_a_init() + Self::swap_min_out() + } + + fn user_b_swap_exact_output_b_to_a() -> u128 { + Self::user_b_init() - Self::exact_output_b_to_a_amount_in() + } + fn vault_a_add() -> u128 { 7_000 } @@ -536,8 +576,7 @@ impl Accounts { definition_id: Ids::token_a_definition(), balance: Balances::user_a_swap_1(), }), - // Both user holdings are now swap signers, so this holding's nonce increments too. - nonce: Nonce(1), + nonce: Nonce(0), } } @@ -616,7 +655,140 @@ impl Accounts { definition_id: Ids::token_b_definition(), balance: Balances::user_b_swap_2(), }), - // Both user holdings are now swap signers, so this holding's nonce increments too. + nonce: Nonce(0), + } + } + + fn pool_definition_swap_exact_output_a_to_b() -> Account { + Account { + program_owner: Ids::amm_program(), + balance: 0_u128, + data: Data::from(&PoolDefinition { + definition_token_a_id: Ids::token_a_definition(), + definition_token_b_id: Ids::token_b_definition(), + vault_a_id: Ids::vault_a(), + vault_b_id: Ids::vault_b(), + liquidity_pool_id: Ids::token_lp_definition(), + liquidity_pool_supply: Balances::pool_lp_supply_init(), + reserve_a: Balances::reserve_a_swap_exact_output_a_to_b(), + reserve_b: Balances::reserve_b_swap_exact_output_a_to_b(), + fees: Balances::fee_tier(), + }), + nonce: Nonce(0), + } + } + + fn vault_a_swap_exact_output_a_to_b() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::reserve_a_swap_exact_output_a_to_b(), + }), + nonce: Nonce(0), + } + } + + fn vault_b_swap_exact_output_a_to_b() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::reserve_b_swap_exact_output_a_to_b(), + }), + nonce: Nonce(0), + } + } + + fn user_a_holding_swap_exact_output_a_to_b() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_swap_exact_output_a_to_b(), + }), + nonce: Nonce(1), + } + } + + fn user_b_holding_swap_exact_output_a_to_b() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::user_b_swap_exact_output_a_to_b(), + }), + nonce: Nonce(0), + } + } + + fn pool_definition_swap_exact_output_b_to_a() -> Account { + Account { + program_owner: Ids::amm_program(), + balance: 0_u128, + data: Data::from(&PoolDefinition { + definition_token_a_id: Ids::token_a_definition(), + definition_token_b_id: Ids::token_b_definition(), + vault_a_id: Ids::vault_a(), + vault_b_id: Ids::vault_b(), + liquidity_pool_id: Ids::token_lp_definition(), + liquidity_pool_supply: Balances::pool_lp_supply_init(), + reserve_a: Balances::reserve_a_swap_exact_output_b_to_a(), + reserve_b: Balances::reserve_b_swap_exact_output_b_to_a(), + fees: Balances::fee_tier(), + }), + nonce: Nonce(0), + } + } + + fn vault_a_swap_exact_output_b_to_a() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::reserve_a_swap_exact_output_b_to_a(), + }), + nonce: Nonce(0), + } + } + + fn vault_b_swap_exact_output_b_to_a() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::reserve_b_swap_exact_output_b_to_a(), + }), + nonce: Nonce(0), + } + } + + fn user_a_holding_swap_exact_output_b_to_a() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_swap_exact_output_b_to_a(), + }), + nonce: Nonce(0), + } + } + + fn user_b_holding_swap_exact_output_b_to_a() -> Account { + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::user_b_swap_exact_output_b_to_a(), + }), nonce: Nonce(1), } } @@ -990,13 +1162,9 @@ fn state_for_amm_tests() -> V03State { state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_init()); // Seed the pool's current-tick account so swaps and syncs can refresh it. Its initial value is // the tick of the opening reserves; swap/sync tests assert it is updated to the new price. - let initial_tick = twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64( - Balances::vault_a_init(), - Balances::vault_b_init(), - )); state.force_insert_account( Ids::current_tick_account(), - Accounts::current_tick_account(initial_tick), + Accounts::current_tick_account(initial_pool_tick()), ); state.force_insert_account( Ids::token_a_definition(), @@ -1118,7 +1286,6 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in, min_amount_out, - token_definition_id_in: Ids::token_a_definition(), deadline: u64::MAX, }; @@ -1134,16 +1301,12 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![ - current_nonce(state, Ids::user_a()), - current_nonce(state, Ids::user_b()), - ], + vec![current_nonce(state, Ids::user_a())], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); @@ -1154,10 +1317,10 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in, min_amount_out, - token_definition_id_in: Ids::token_b_definition(), deadline: u64::MAX, }; + // Token B is the input, so the B holding occupies the signed input slot. let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ @@ -1165,21 +1328,17 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), - Ids::user_a(), Ids::user_b(), + Ids::user_a(), Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![ - current_nonce(state, Ids::user_a()), - current_nonce(state, Ids::user_b()), - ], + vec![current_nonce(state, Ids::user_b())], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); @@ -1391,6 +1550,54 @@ fn pool_definition(account: &Account) -> PoolDefinition { PoolDefinition::try_from(&account.data).expect("expected pool definition") } +fn initial_pool_tick() -> i32 { + twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64( + Balances::vault_a_init(), + Balances::vault_b_init(), + )) +} + +fn assert_initial_swap_state(state: &V03State) { + assert_eq!(state.get_account_by_id(Ids::config()), Accounts::config()); + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_init() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_init() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_init() + ); + assert_eq!( + state.get_account_by_id(Ids::user_a()), + Accounts::user_a_holding() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding() + ); + assert_eq!( + state.get_account_by_id(Ids::current_tick_account()), + Accounts::current_tick_account(initial_pool_tick()) + ); +} + +fn assert_current_tick_matches_pool(state: &V03State) { + let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition())); + let tick_account = twap_oracle_core::CurrentTickAccount::try_from( + &state.get_account_by_id(Ids::current_tick_account()).data, + ) + .expect("current tick account must hold a valid CurrentTickAccount"); + let expected_tick = twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64( + pool.reserve_a, + pool.reserve_b, + )); + assert_eq!(tick_account.tick, expected_tick); +} + fn fungible_total_supply(account: &Account) -> u128 { let definition = TokenDefinition::try_from(&account.data).expect("expected token definition"); let TokenDefinition::Fungible { @@ -2517,10 +2724,10 @@ fn amm_swap_b_to_a() { let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), - token_definition_id_in: Ids::token_b_definition(), deadline: u64::MAX, }; + // Token B is the input, so the B holding occupies the signed input slot. let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ @@ -2528,18 +2735,17 @@ fn amm_swap_b_to_a() { Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), - Ids::user_a(), Ids::user_b(), + Ids::user_a(), Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![Nonce(0), Nonce(0)], + vec![Nonce(0)], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); @@ -2573,7 +2779,6 @@ fn amm_swap_a_to_b() { let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), - token_definition_id_in: Ids::token_a_definition(), deadline: u64::MAX, }; @@ -2589,13 +2794,12 @@ fn amm_swap_a_to_b() { Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![Nonce(0), Nonce(0)], + vec![Nonce(0)], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); @@ -2646,7 +2850,6 @@ fn amm_swap_exact_output_refreshes_current_tick() { let instruction = amm_core::Instruction::SwapExactOutput { exact_amount_out: Balances::swap_min_out(), max_amount_in: Balances::swap_amount_in(), - token_definition_id_in: Ids::token_a_definition(), deadline: u64::MAX, }; @@ -2662,16 +2865,36 @@ fn amm_swap_exact_output_refreshes_current_tick() { Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![Nonce(0), Nonce(0)], + vec![Nonce(0)], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_swap_exact_output_a_to_b() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_swap_exact_output_a_to_b() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_swap_exact_output_a_to_b() + ); + assert_eq!( + state.get_account_by_id(Ids::user_a()), + Accounts::user_a_holding_swap_exact_output_a_to_b() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding_swap_exact_output_a_to_b() + ); + // The swap refreshed the pool's TWAP current tick to the post-swap spot price, computed from // the reserves the swap actually settled on. let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition())); @@ -2690,6 +2913,237 @@ fn amm_swap_exact_output_refreshes_current_tick() { ); } +#[test] +fn amm_swap_exact_output_b_to_a_signs_only_input() { + let mut state = state_for_amm_tests(); + + let instruction = amm_core::Instruction::SwapExactOutput { + exact_amount_out: Balances::swap_min_out(), + max_amount_in: Balances::swap_amount_in(), + deadline: u64::MAX, + }; + + // Token B is the input, so the B holding occupies the signed input slot; only it is signed. + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_b(), + Ids::user_a(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![Nonce(0)], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_swap_exact_output_b_to_a() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_swap_exact_output_b_to_a() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_swap_exact_output_b_to_a() + ); + assert_eq!( + state.get_account_by_id(Ids::user_a()), + Accounts::user_a_holding_swap_exact_output_b_to_a() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding_swap_exact_output_b_to_a() + ); + + let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition())); + let required_input = pool + .reserve_b + .checked_sub(Balances::vault_b_init()) + .expect("swap should increase input-token reserve"); + let user_a_balance = fungible_balance(&state.get_account_by_id(Ids::user_a())); + let user_b_balance = fungible_balance(&state.get_account_by_id(Ids::user_b())); + + assert_eq!( + pool.reserve_a, + Balances::vault_a_init() - Balances::swap_min_out() + ); + assert_ne!(required_input, 0); + assert!(required_input <= Balances::swap_amount_in()); + assert_eq!( + Balances::user_b_init() - user_b_balance, + required_input, + "user debit must equal pool input reserve increase" + ); + assert_eq!( + user_a_balance, + Balances::user_a_init() + Balances::swap_min_out() + ); + assert_current_tick_matches_pool(&state); +} + +#[test] +fn amm_swap_exact_input_requires_input_signature() { + let mut state = state_for_amm_tests(); + + let instruction = amm_core::Instruction::SwapExactInput { + swap_amount_in: Balances::swap_amount_in(), + min_amount_out: Balances::swap_min_out(), + deadline: u64::MAX, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, 0), + Err(LeeError::ProgramExecutionFailed(_)) + )); + + assert_initial_swap_state(&state); +} + +#[test] +fn amm_swap_exact_input_rejects_when_only_output_holding_signed() { + let mut state = state_for_amm_tests(); + + let instruction = amm_core::Instruction::SwapExactInput { + swap_amount_in: Balances::swap_amount_in(), + min_amount_out: Balances::swap_min_out(), + deadline: u64::MAX, + }; + + // Input slot holds token A but only the output (B) holding is signed: the input is unsigned, so + // the swap must be rejected. + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![Nonce(0)], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, 0), + Err(LeeError::ProgramExecutionFailed(_)) + )); + + assert_initial_swap_state(&state); +} + +#[test] +fn amm_swap_exact_output_requires_input_signature() { + let mut state = state_for_amm_tests(); + + let instruction = amm_core::Instruction::SwapExactOutput { + exact_amount_out: Balances::swap_min_out(), + max_amount_in: Balances::swap_amount_in(), + deadline: u64::MAX, + }; + + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, 0), + Err(LeeError::ProgramExecutionFailed(_)) + )); + + assert_initial_swap_state(&state); +} + +#[test] +fn amm_swap_exact_output_rejects_when_only_output_holding_signed() { + let mut state = state_for_amm_tests(); + + let instruction = amm_core::Instruction::SwapExactOutput { + exact_amount_out: Balances::swap_min_out(), + max_amount_in: Balances::swap_amount_in(), + deadline: u64::MAX, + }; + + // Input slot holds token A but only the output (B) holding is signed: the input is unsigned, so + // the swap must be rejected. + let message = public_transaction::Message::try_new( + Ids::amm_program(), + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![Nonce(0)], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]); + let tx = PublicTransaction::new(message, witness_set); + assert!(matches!( + state.transition_from_public_transaction(&tx, 0, 0), + Err(LeeError::ProgramExecutionFailed(_)) + )); + + assert_initial_swap_state(&state); +} + #[test] fn amm_sync_reserves_updates_pool_and_current_tick() { let mut state = state_for_amm_tests(); @@ -2783,7 +3237,6 @@ fn amm_swap_rejects_expired_deadline() { let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), - token_definition_id_in: Ids::token_a_definition(), deadline: deadline_ms, }; @@ -2799,13 +3252,12 @@ fn amm_swap_rejects_expired_deadline() { Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![Nonce(0), Nonce(0)], + vec![Nonce(0)], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); let tx = PublicTransaction::new(message, witness_set); assert!(matches!( state.transition_from_public_transaction(&tx, 0, block_timestamp_ms), @@ -2823,7 +3275,6 @@ fn amm_swap_exact_output_rejects_expired_deadline() { let instruction = amm_core::Instruction::SwapExactOutput { exact_amount_out: Balances::swap_min_out(), max_amount_in: Balances::swap_amount_in(), - token_definition_id_in: Ids::token_a_definition(), deadline: deadline_ms, }; @@ -2839,16 +3290,12 @@ fn amm_swap_exact_output_rejects_expired_deadline() { Ids::current_tick_account(), CLOCK_01_PROGRAM_ACCOUNT_ID, ], - vec![ - current_nonce(&state, Ids::user_a()), - current_nonce(&state, Ids::user_b()), - ], + vec![current_nonce(&state, Ids::user_a())], instruction, ) .unwrap(); - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); let tx = PublicTransaction::new(message, witness_set); assert!(matches!( state.transition_from_public_transaction(&tx, 0, block_timestamp_ms),