# Liquidity borrowed from or repaid to parent nodes is not always minted or burned in the uniswap pool

Bug Deep Dive #21 · 17 December 2025

Bug Deep Dives · [The Contest Academy](https://0xsimao.com/the-contest-academy) · 0xSimao

---

| Title | Liquidity borrowed from or repaid to parent nodes is not always minted or burned in the uniswap pool |
| Reward | $1196, 3 dups |
| Contest | Ammplify - 2 Sep 2025 on Sherlock |
| Author | panprog |
| Context | Accounting |

When a node does not have enough liquidity, it borrows from its parent. Since the parent's liquidity equals the liquidity of both children, borrowing increases the liquidity of the current node and of its sibling. This happens in `solveLiq`:

```solidity
// We need to borrow liquidity from our parent node.
Node storage sibling = data.node(iter.key.sibling());
Node storage parent = data.node(iter.key.parent());
int128 borrow = -netLiq;
if (borrow < int128(data.liq.compoundThreshold)) {
    // We borrow at least this amount.
    borrow = int128(data.liq.compoundThreshold);
}
parent.liq.preLend += borrow;
parent.liq.dirty = true;
node.liq.borrowed += uint128(borrow);
node.liq.dirty = true;
sibling.liq.preBorrow += borrow;
sibling.liq.dirty = true;
```

Notice that the sibling's liquidity is not actually modified — only `preBorrow` changes, which does not affect the `net()` calculation. In many cases siblings are not part of the route either, so in the settle walker, where Uniswap liquidity is minted and burned, they are never traversed and the corresponding Uniswap liquidity is never minted or burned.

This leaves the accounting wrong and the contract's portfolio of Uniswap positions wrong with it: price changes are calculated as though the contract held positions it does not hold.

The issue can also be used by any malicious user to manipulate pool prices and steal all the contract's funds, which includes every taker's collateral and the collected fees not yet claimed or compounded.

**Weaponizing it**

1\. User has 100e18 maker liquidity in 480..960 range;\
2\. Taker takes 100e18 liquidity in the 720..960 range;\
3\. Since there is not enough liquidity available in the 720..960 range, it is borrowed from the 480..960 range;\
4\. 100e18 of 480..960 liquidity is burned;\
5\. 100e18 worth of 720..960 liquidity is given to taker.

Notice that the 480..720 liquidity is never minted. Ammplify now owes 100e18 liquidity in the 480..960 range but **holds 0 Uniswap positions**; the taker owes Ammplify 100e18 liquidity in the 720..960 range; and Ammplify holds the asset distribution of 100e18 liquidity in the 480..720 range. Once the liquidity is repaid, Ammplify may be insolvent and unable to mint the 480..960 liquidity owed to the user, because its token distribution is wrong and it never earned the fees the Uniswap position would have.

**Alpha:** walk every flow through the contract, and write the state down when it gets complicated, to confirm the accounting adds up.

**Conclusion**

This finding would earn you **\$1196**. The key is walking the flow and checking that every quantity is accounted for — here, a liquidity mint was simply missing.

[**Full Report**](https://audits.sherlock.xyz/contests/1054/report)\
[**Codebase**](https://github.com/itos-finance/AmmplifyPublic/tree/beb942cfbd54990fc9434fbfa99a1fc251d73689)

---

Newer: [The protocol doesnt support weird tokens like Tether Gold (that return false on every operation)](https://0xsimao.com/the-contest-academy/bug-deep-dive-22) · Older: [User can lose all funds when creating or increasing compounded Maker position due to share inflation](https://0xsimao.com/the-contest-academy/bug-deep-dive-20)
