From bf1f76b051ad4254b7a0810b22db80b79403bc27 Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:14:15 +0200 Subject: [PATCH] fix(modules/amm): swap plan must use the pool's stored vault ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The swap-submission plan derived the pool's vaults from the canonical token order (compute_vault_pda(pool, canonical_token_a/b) via derive_pair), but the guest asserts the provided vaults against the pool's stored vault_a_id / vault_b_id, which are in the pool's *creation* order. compute_pool_pda_seed canonicalizes, so the pool address is order-independent, but NewDefinition stores def_a/def_b (and their vaults) as created — so for a pool created in non-canonical order, the plan put vault_b in the vault_a slot and the guest panicked with "Vault A was not provided", reverting the swap. Read the pool account in swapExactInput and pass its data to swap_exact_in_plan, which now uses pool.vault_a_id / pool.vault_b_id verbatim for the vault slots (pool / current_tick / clock stay from derive_pair, since those are order-independent). This restores the order-agnostic behavior the original swap client had before it was rewired onto the canonical derive_pair in 737b2f6. Adds a regression test that builds a non-canonically-created pool (stored def_a = the smaller-valued token) and asserts the plan emits the pool's stored vaults, guarding that they differ from the canonical derivation. --- modules/amm/ffi/src/api/request.rs | 4 +++ modules/amm/ffi/src/api/swap.rs | 17 +++++++-- modules/amm/ffi/src/api/tests.rs | 54 ++++++++++++++++++++++++++++- modules/amm/src/amm_module_impl.cpp | 19 ++++++++-- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/modules/amm/ffi/src/api/request.rs b/modules/amm/ffi/src/api/request.rs index fee626e..4fe9046 100644 --- a/modules/amm/ffi/src/api/request.rs +++ b/modules/amm/ffi/src/api/request.rs @@ -111,6 +111,10 @@ pub struct SwapExactInPlanRequest { pub amount_in: String, pub min_out: String, pub deadline_ms: String, + /// Pool account data (hex Borsh `PoolDefinition`) — its stored `vault_a_id` / + /// `vault_b_id` are used verbatim (the guest asserts the vaults in the pool's + /// creation order, which needn't match the canonical token order). + pub pool_data: String, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] diff --git a/modules/amm/ffi/src/api/swap.rs b/modules/amm/ffi/src/api/swap.rs index 0ec2d5c..dba7276 100644 --- a/modules/amm/ffi/src/api/swap.rs +++ b/modules/amm/ffi/src/api/swap.rs @@ -287,6 +287,18 @@ pub(super) fn swap_exact_in_plan(request: SwapExactInPlanRequest) -> Result(&bytes).ok()) + else { + return Ok(json!({ "status": "error", "code": "no_pool" })); + }; + let user_input_holding = account_id_from_hex(&request.user_input_holding_id, "user input holding id")?; let user_output_holding = @@ -307,8 +319,8 @@ pub(super) fn swap_exact_in_plan(request: SwapExactInPlanRequest) -> Result