diff --git a/artifacts/amm-idl.json b/artifacts/amm-idl.json index 1c23865..d191404 100644 --- a/artifacts/amm-idl.json +++ b/artifacts/amm-idl.json @@ -423,13 +423,13 @@ "init": false }, { - "name": "user_holding_a", + "name": "user_input_holding", "writable": true, - "signer": false, + "signer": true, "init": false }, { - "name": "user_holding_b", + "name": "user_output_holding", "writable": 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,13 +490,13 @@ "init": false }, { - "name": "user_holding_a", + "name": "user_input_holding", "writable": true, - "signer": false, + "signer": true, "init": false }, { - "name": "user_holding_b", + "name": "user_output_holding", "writable": 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 09bf564..4ca2765 100644 --- a/programs/amm/core/src/lib.rs +++ b/programs/amm/core/src/lib.rs @@ -174,15 +174,16 @@ pub enum Instruction { /// Swap some quantity of tokens while maintaining the Pool constant product. /// - /// Swap direction is selected by `token_definition_id_in`; the selected input holding must be - /// authorized so the downstream token transfer can debit it. + /// 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 (initialized) - /// - User Holding Account for Token B (initialized); the input holding 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 @@ -190,7 +191,6 @@ 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, }, @@ -198,15 +198,16 @@ pub enum Instruction { /// Swap tokens specifying the exact desired output amount while maintaining the Pool constant /// product. /// - /// Swap direction is selected by `token_definition_id_in`; the selected input holding must be - /// authorized so the downstream token transfer can debit it. + /// 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 (initialized) - /// - User Holding Account for Token B (initialized); the input holding 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 @@ -214,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 f3c12e3..a05db60 100644 --- a/programs/amm/methods/guest/src/bin/amm.rs +++ b/programs/amm/methods/guest/src/bin/amm.rs @@ -299,8 +299,8 @@ mod amm { /// Swap some quantity of tokens while maintaining the pool constant product. /// - /// `token_definition_id_in` selects the swap input token; the selected input holding must be - /// authorized for the downstream token transfer. + /// 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" @@ -315,16 +315,15 @@ mod amm { vault_a: AccountWithMetadata, #[account(mut)] vault_b: AccountWithMetadata, + #[account(mut, signer)] + user_input_holding: AccountWithMetadata, #[account(mut)] - user_holding_a: AccountWithMetadata, - #[account(mut)] - user_holding_b: AccountWithMetadata, + 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( @@ -332,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) @@ -347,8 +345,8 @@ mod amm { /// Swap tokens specifying the exact desired output amount. /// - /// `token_definition_id_in` selects the swap input token; the selected input holding must be - /// authorized for the downstream token transfer. + /// 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" @@ -363,16 +361,15 @@ mod amm { vault_a: AccountWithMetadata, #[account(mut)] vault_b: AccountWithMetadata, + #[account(mut, signer)] + user_input_holding: AccountWithMetadata, #[account(mut)] - user_holding_a: AccountWithMetadata, - #[account(mut)] - user_holding_b: AccountWithMetadata, + 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( @@ -380,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 abe709b..6d6896a 100644 --- a/programs/integration_tests/tests/amm.rs +++ b/programs/integration_tests/tests/amm.rs @@ -1286,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, }; @@ -1318,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![ @@ -1329,8 +1328,8 @@ 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, ], @@ -2725,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![ @@ -2736,8 +2735,8 @@ 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, ], @@ -2780,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, }; @@ -2852,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, }; @@ -2923,10 +2920,10 @@ fn amm_swap_exact_output_b_to_a_signs_only_input() { 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_b_definition(), 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![ @@ -2934,8 +2931,8 @@ fn amm_swap_exact_output_b_to_a_signs_only_input() { 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, ], @@ -2995,61 +2992,6 @@ fn amm_swap_exact_output_b_to_a_signs_only_input() { assert_current_tick_matches_pool(&state); } -#[test] -fn amm_swap_exact_output_dual_signed_uses_token_definition_id_in() { - 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(), - token_definition_id_in: Ids::token_b_definition(), - 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![Nonce(0), Nonce(0)], - instruction, - ) - .unwrap(); - - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &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() - ); - let mut expected_user_a = Accounts::user_a_holding_swap_exact_output_b_to_a(); - expected_user_a.nonce = Nonce(1); - assert_eq!(state.get_account_by_id(Ids::user_a()), expected_user_a); - assert_eq!( - state.get_account_by_id(Ids::user_b()), - Accounts::user_b_holding_swap_exact_output_b_to_a() - ); - assert_current_tick_matches_pool(&state); -} - #[test] fn amm_swap_exact_input_requires_input_signature() { let mut state = state_for_amm_tests(); @@ -3057,7 +2999,6 @@ fn amm_swap_exact_input_requires_input_signature() { 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, }; @@ -3089,16 +3030,17 @@ fn amm_swap_exact_input_requires_input_signature() { } #[test] -fn amm_swap_exact_input_rejects_token_definition_id_in_mismatch() { +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(), - token_definition_id_in: Ids::token_a_definition(), 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![ @@ -3126,137 +3068,6 @@ fn amm_swap_exact_input_rejects_token_definition_id_in_mismatch() { assert_initial_swap_state(&state); } -#[test] -fn amm_swap_exact_input_rejects_swapped_user_holding_slots() { - 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(), - token_definition_id_in: Ids::token_b_definition(), - 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_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); - assert!(matches!( - state.transition_from_public_transaction(&tx, 0, 0), - Err(LeeError::ProgramExecutionFailed(_)) - )); - - assert_initial_swap_state(&state); -} - -#[test] -fn amm_swap_exact_input_dual_signed_uses_token_definition_id_in() { - 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(), - token_definition_id_in: Ids::token_b_definition(), - 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![Nonce(0), Nonce(0)], - instruction, - ) - .unwrap(); - - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &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_1() - ); - assert_eq!( - state.get_account_by_id(Ids::vault_a()), - Accounts::vault_a_swap_1() - ); - assert_eq!( - state.get_account_by_id(Ids::vault_b()), - Accounts::vault_b_swap_1() - ); - let mut expected_user_a = Accounts::user_a_holding_swap_1(); - expected_user_a.nonce = Nonce(1); - assert_eq!(state.get_account_by_id(Ids::user_a()), expected_user_a); - assert_eq!( - state.get_account_by_id(Ids::user_b()), - Accounts::user_b_holding_swap_1() - ); -} - -#[test] -fn amm_swap_exact_input_rejects_dual_signed_unknown_token_definition_id_in() { - 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(), - token_definition_id_in: Ids::token_lp_definition(), - 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![Nonce(0), Nonce(0)], - instruction, - ) - .unwrap(); - - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &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(); @@ -3264,7 +3075,6 @@ fn amm_swap_exact_output_requires_input_signature() { 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, }; @@ -3296,16 +3106,17 @@ fn amm_swap_exact_output_requires_input_signature() { } #[test] -fn amm_swap_exact_output_rejects_token_definition_id_in_mismatch() { +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(), - token_definition_id_in: Ids::token_a_definition(), 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![ @@ -3333,83 +3144,6 @@ fn amm_swap_exact_output_rejects_token_definition_id_in_mismatch() { assert_initial_swap_state(&state); } -#[test] -fn amm_swap_exact_output_rejects_swapped_user_holding_slots() { - 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(), - token_definition_id_in: Ids::token_b_definition(), - 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_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); - 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_dual_signed_unknown_token_definition_id_in() { - 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(), - token_definition_id_in: Ids::token_lp_definition(), - 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![Nonce(0), Nonce(0)], - instruction, - ) - .unwrap(); - - let witness_set = - public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &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(); @@ -3503,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, }; @@ -3542,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, };