# All taker collateral and collected fees can be stolen by re-entering via RFTLib.settle to manipulate uniswap spot price

Bug Deep Dive #16 · 12 December 2025

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

---

| Title | All taker collateral and collected fees can be stolen by re-entering via RFTLib.settle to manipulate uniswap spot price |
| Reward | $4432, Unique |
| Contest | Ammplify - 2 Sep 2025 on Sherlock |
| Author | panprog |
| Context | Spot Price Manipulation |

When a user adds maker liquidity, via `newMaker` or `adjustMaker`, the amounts of token0 and token1 required from them are calculated into `data.xBalance` and `data.yBalance`. The amounts actually spent to mint the liquidity are never checked: the contract simply sends whatever the Uniswap pool asks for in the mint callback. When the funds are settled for the maker liquidity change, the following functions are called:

```solidity
RFTLib.settle(msg.sender, tokens, balances, rftData);
PoolWalker.settle(pInfo, asset.lowTick, asset.highTick, data);

// RFTLib.settle()

    // If the payer is an RFTPayer, we make the request now.
    if (isRFTPayer) {
        cbData = IRFTPayer(payer).tokenRequestCB(tokens, balanceChanges, data);
    }
```

This lets the user manipulate the Uniswap spot price through the `RFTLib` callback. `PoolWalker.settle` then mints the required liquidity using amounts of token0 and token1 different from those calculated and collected from the user — and the total value of those tokens is always higher than the value received. Manipulating the price back to its initial value leaves the attacker in profit, having stolen token0 or token1 from the contract.

Because the contract uses a Diamond proxy, it holds every token balance at the same address used to mint Uniswap liquidity. The funds at risk are:

- All taker collateral
- All fees collected from the Uniswap pool

**Alpha:** note that this is only possible because a single contract holds all the funds. Think the attack through to the end, and always check whether moving the pool's spot price can leave an attacker better off.

**Weaponizing it**

Note: $a_0$ is the amount of token0, $a_1$ the amount of token1.\
1\. Current price = 100;\
2\. Takers have a total collateral of 100 a0 and 10000 a1 (these are contract balances);\
3\. There is liquidity of 1 in the range \[100;10000];\
4\. Attacker adds 111 maker liquidity in the range \[100;10000], paying 10 a0;\
5\. In the RFTLib.settle attacker manipulates the price to 10000:\
 5.1. Attacker pays a1: -90;\
 5.2. Attacker receives a0: +0.09;\
6\. Attacker returns from the RFTLib callback, 111 liquidity is minted to Amplify, taking 10000 a1 from it;\
7\. Attacker manipulates the price back to 100:\
 6.1. Attacker pays a0: -10;\
 6.2. Attacker receives a1: +10000;\
8\. Attacker waits for jit penalty time to pass and withdraws his 111 maker liquidity, receiving 10 a0;\
\
**Contract balances:**\
• before: 100 a0 + 10000 a1 = \$20000\
• after: 110 a0 + 0 a1 = \$11000\
\
**Attacker balances:**\
• a0: -10 + 0.09 - 10 + 10 = -9.91 \[- \$991]\
• a1: -90 + 10000 = +9910 \[+ \$9910]\
• Uniswap manipulation fees (0.3%): $(90 + 10000) \cdot 0.003 = -30$\
Total: $9910 - 991 - 30 = +8889$ USD

**Conclusion**

This finding would earn you **\$4432**. It is elaborate, but the lesson is simple: always check that it is safe to read the current reserves or spot price of an integrated Uniswap pool.

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

---

Newer: [Incorrect inside fees calculation for uninitialized uniswap ticks causes positions funds being stuck in the contract](https://0xsimao.com/the-contest-academy/bug-deep-dive-17) · Older: [Ineffective minimum order size check for ASK limit orders can lead to Denial of Service](https://0xsimao.com/the-contest-academy/bug-deep-dive-15)
