# An attacker possesses the capability to exhaust the entirety of liquidity within the stable swap pools by manipulating the buy function, specifically by setting the asset_in parameter equal to the asset_out parameter 

HydraDX stableswap buy() allowed asset_in == asset_out, making amount_in compute to 1, letting attacker drain pool liquidity for negligible cost.

- Payout: Contest reward
- Severity: high
- Program: HydraDX
- Credited: castle\_chain
- Disclosed: 2024-04-10
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2024-02-hydradx-findings/issues/58
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-hydradx%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:solidity technique:source-review vuln:business-logic

**Target:** Stableswap pallet buy() extrinsic
**Root cause:** buy() lacks a check that asset_in != asset_out; when equal, calculate_in_given_out computes new_reserve_in == reserves[idx_in], yielding amount_in = 0 then saturating_add(1) = 1, so attacker pays ~1 unit for any amount_out.
**Steps:**
1. Call `Stableswap::buy(pool_id, asset_out=asset_a, asset_in=asset_a, amount_out=to_precision!(20, dec_a), max_sell_amount=to_precision!(31, dec_a))`.
2. calculate_in_given_out sees idx_in == idx_out, new_reserve_in == reserves[idx_in], amount_in = 0, then saturating_add(1) => 1.
3. Fee applied => amount_in ≈ 1.001; attacker receives amount_out (e.g. 20e18) from pool.
4. Repeat 5 times to drain ~100e18 asset_a from a 100e18 pool (POC logs show pool balance 100e18 -> 28).
**How found:** Code review of buy() and calculate_in_given_out; noticed missing asset_in != asset_out validation and traced math to see new_reserve_in equals old reserve when indices match, producing amount_in=1.
**Exploitation:** Attacker with any balance calls buy() with asset_in == asset_out and large amount_out; pays ~1 unit plus fee and receives the full amount_out from the pool, repeating until pool drained. No permissions or capital needed beyond gas.
**Escalation:** Repeat across all stableswap pools to drain all assets.
**Insight:** In AMM math, when in/out asset indices can be equal, reserve-difference formulas collapse to zero; always check for same-asset swaps and verify the saturating_add(1) minimum doesn't become the entire cost.
