Reproduced Exploit
BMX — adjustToTick stops when nextTick equals the target
A swap from tick 90 lands on initialized tick 60. nextInitializedTickWithinOneWord returns 60. The break is nextTick <= tick, so 60 <= 60 leaves the loop before the cross. The gauge stores 60. Position [60, 120) is still in range and is minted 100e18. Position [0, 60) receives 0. Uniswap would stor…
Chain
Other
Category
logic
Date
Sep 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 62812-h-5-rangepooladjusttotick-desyncs-when-nexttick-tick-leading. Standalone Foundry PoC and full write-up: 62812-h-5-rangepooladjusttotick-desyncs-when-nexttick-tick-leading_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/incorrect-calculation · vuln/logic/wrong-condition
Key info#
| Protocol | BMX Deli Swap |
| Finding | Sherlock H-5, AuditVault 62812 |
| Vulnerable code | RangePool.adjustToTick |
| Harm | The gauge stores tick 60. The out-of-range position is paid 100e18. The in-range position is paid 0. |
| Chain | local synthetic |
TL;DR#
A swap from tick 90 lands on initialized tick 60. nextInitializedTickWithinOneWord returns 60. The break is nextTick <= tick, so 60 <= 60 leaves the loop before the cross. The gauge stores 60. Position [60, 120) is still in range and is minted 100e18. Position [0, 60) receives 0. Uniswap would store 59 after the cross. The control uses nextTick < tick, stores 59, and pays the 100e18 to the honest position.
The vulnerable code#
(int24 nextTick, bool isInit) =
this.nextInitializedTickWithinOneWord(currentTick, tickSpacing, true);
// @> VULN: nextTick == tick_ breaks, so tick 60 is not crossed
if (nextTick <= tick_) {
break;
}
The fix breaks only when nextTick < tick, so equality still crosses and the active tick becomes nextTick - 1.
Root cause#
The gauge and the pool disagree on whether a price that sits on an initialized tick has crossed it. The <= break treats "arrived on the tick" as "stop before the tick". Fee payment then uses the wrong in-range position.
Preconditions#
Active tick starts at 90. Ticks 0, 60, and 120 are initialized. The attacker is [60, 120). The honest LP is [0, 60). The swap target is 60. One block. No warp.
Attack walkthrough#
- The pool is configured at tick 90.
onSwap(60)callsadjustToTick(60, 60).- The bitmap returns nextTick 60.
60 <= 60breaks.crossedstays false. The stored tick is 60.pay(100e18)mints 100e18 to the attacker. The honest LP receives 0.
Diagrams#
Impact#
The attacker is paid 100e18 of fees. The honest in-range position is paid 0. The control stores tick 59, which is below 60, and pays the honest position 100e18.
Remediation#
Break only when nextTick < tick, and store the post-cross tick nextTick - 1 when moving down.
How to reproduce#
cd 62812-h-5-rangepooladjusttotick-desyncs-when-nexttick-tick-leading_exp
forge test -vv
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/62812-h-5-rangepooladjusttotick-desyncs-when-nexttick-tick-leading.md
- Sherlock contest: https://audits.sherlock.xyz/contests/1154
- The break
if (nextTick <= tick)is the line quoted fromRangePool.adjustToTick. The fixed pool inmorphex-labs/deli-swap-contractscompares the sqrt price and crosses when the price reaches the tick.
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 62812-h-5-rangepooladjusttotick-desyncs-when-nexttick-tick-leading_exp (evm-hack-registry mirror).
- AuditVault finding: 62812-h-5-rangepooladjusttotick-desyncs-when-nexttick-tick-leading.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.