fix(modules/amm): swap plan must use the pool's stored vault ids

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.
This commit is contained in:
r4bbit
2026-08-06 14:31:49 +02:00
parent a389dc6056
commit bf1f76b051
4 changed files with 89 additions and 5 deletions
+17 -2
View File
@@ -609,13 +609,28 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex,
return {};
}
// amm_swap_exact_in_plan resolves the pool, reorders holdings to the pool's
// canonical def order, encodes SwapExactInput, and returns a ready-to-submit plan.
// Read the pool so the plan can use its stored vault ids (the guest asserts
// the vaults in the pool's creation order — see amm_swap_exact_in_plan).
const FfiResult poolId = call(amm_pool_id, json{
{"ammProgramId", net.amm_program_id},
{"tokenInId", def_a_hex},
{"tokenOutId", def_b_hex},
});
if (!poolId.ok) {
AMM_TRACE("swapExactInput: FAIL amm_pool_id");
return {};
}
const json pool = readPublicAccount(jStr(poolId.value, "poolId"));
const std::string pool_data = jStr(pool.value("account", json::object()), "data");
// amm_swap_exact_in_plan resolves the pool accounts, encodes SwapExactInput,
// and returns a ready-to-submit plan.
const FfiResult planResult = call(amm_swap_exact_in_plan, json{
{"ammProgramId", net.amm_program_id},
{"tokenInId", def_a_hex},
{"tokenOutId", def_b_hex},
{"config", config},
{"poolData", pool_data},
{"userInputHoldingId", user_input_holding_hex},
{"userOutputHoldingId", user_output_holding_hex},
{"amountIn", amount_in_decimal},