Reproduced Exploit
BeatXswap LiquidityVestingConvert — UniV3 `slot0` spot oracle
BeatSwap's LV Convert lets users deposit USDT. 75% goes to a treasury; 25% is paired with protocol-owned BTX and minted as a Pancake V3 LP. The BTX amount is sized by _calculateQuote(), which converts USDT→BTX from the 0.01% pool's live slot0 sqrtPriceX96. There is no TWAP and no sanity bound. amou…
Loss
~2.98M–3.07M BTX (~$63.7k–$77.5k). Live attacker net 63,704.84 USDT. Teaching PoC drains 163,432.97 BTX from…
Chain
BNB Chain
Category
oracle
Date
Sep 2026
Source
Crypto Training
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. Crypto Training original detection and analysis (live Twitter/X security-alert intake — not from DeFiHackLabs). Standalone Foundry PoC, offline
anvil_state.json, and full write-up: 2026-09-BeatXswapVestingOracle_exp in theevm-hack-registrymirror.
Vulnerability classes: vuln/oracle/spot-price · vuln/oracle/single-source · vuln/oracle/price-manipulation · vuln/logic/price-calculation
Reproduction: Foundry fork at BSC block 120873719 (one before the drain).
deal()seeds the Moolah/Infinity flash inventory, dumps BTX into the 0.01% oracle pool, thendeposit()mints LP from the vesting contracts' own BTX at the crashedslot0quote. Offlineanvil_state.json+_shared/run_poc.sh[PASS]. Alerts: SlowMist · DefimonAlerts.
Key info#
| Loss | ~2.98M–3.07M BTX (~$63.7k–$77.5k). Live attacker net 63,704.84 USDT. Teaching PoC drains 163,432.97 BTX from the two vesting contracts (output.txt) |
| Chain | BNB Smart Chain (chainId 56) |
| Protocol | BeatXswap / BeatSwap — LV Convert (liquidity-backed vesting) |
| Date | 2026-09-09 (alerted 2026-09-11) |
| Attacker EOA | 0x67B2f08683A735cfE6f6E57fA86909b62218C2a1 |
| Attack contract | 0xafF5A574941981CF7f994F2820f7FA26FE031DeD (created in the attack tx) |
| Victim (unlimited) | 0x1e647FAADb05f2124BFCcFC003EDc06D1A90bf5D LiquidityVestingConvert |
| Victim (one-time) | 0x9a7A92240FBAc4030b65A6E61239928d6Bcc716F LiquidityVestingConvertOnce |
| Oracle / LP pool | Pancake V3 BTX/USDT 0.01% 0xA5Db84d7BCcb799fb31bd3c417D04d5bC29Da96D |
| Dump pool | Pancake V3 BTX/USDT 0.30% 0x996A155A2BE7729Ae90884795EA61691FBE84079 |
| BTX / USDT | 0xAa242a47F4cC074E59cbC7D65309B1F21202AaA3 · 0x55d398326f99059fF775485246999027B3197955 |
| Flash lenders (live) | Moolah 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C · Pancake Infinity Vault 0x238a358808379702088667322f80aC48bAd5e6c4 |
| Attack tx | 0xcc71a3bb131c73462b0f25533070113a63c85e942a22e18bc945eec184eb5799 @ block 120873720 |
| Compiler | Solidity v0.8.27+commit.40a35a09 (verified, optimizer 200) |
| Bug class | Spot-price oracle — _calculateQuote() reads IUniswapV3Pool.slot0() sqrtPriceX96 with no TWAP, no deviation bound, no second feed |
TL;DR#
BeatSwap's LV Convert lets users deposit USDT. 75% goes to a treasury; 25% is paired with protocol-owned BTX and minted as a Pancake V3 LP. The BTX amount is sized by _calculateQuote(), which converts USDT→BTX from the 0.01% pool's live slot0 sqrtPriceX96. There is no TWAP and no sanity bound. amount1Min is 97% of that already-manipulated quote, so it cannot protect the mint.
The attacker flash-borrowed ~6M BTX (Pancake Infinity) plus USDT (Moolah), dumped BTX into the V3 book to crash sqrtPriceX96, then called deposit(10_000e18) on the unlimited converter and deposit(2_000e18) on the one-time converter. At the crashed tick the converters minted LP using their own BTX reserves. Reversing the dump extracted that BTX through the pool. Live net ~63.7k USDT; SlowMist counted ~2.98M BTX / $77.5k.
Teaching PoC at block 120873719: seed 150k BTX + 15k USDT, dump into the oracle pool, deposit() both victims. 163,432.97 BTX leaves the vesting contracts (output.txt).
Background#
LV Convert is BeatSwap's public-round distribution: deposit USDT, receive a 180-day locked BTX–USDT LP plus linearly vesting BTX (with a 9.8% bonus). Protocol BTX is supplied in advance so depositors do not take swap slippage — which means a manipulated quote spends the protocol's BTX, not the user's.
Two live converters share the same oracle:
LiquidityVestingConvert— min 10,000 USDT, repeatable (0x1e647…).LiquidityVestingConvertOnce— exact 2,000 USDT, one shot (0x9a7A…).
Both hardcode TARGET_POOL = 0xA5Db84d7… (Pancake V3 USDT/BTX fee 100).
The vulnerable code#
1. Quote is a single slot0 read#
sources/LiquidityVestingConvert_1e647F/project_contracts_LiquidityVestingConvert.sol — _calculateQuote:
function _calculateQuote(uint256 usdtAmountIn) internal view returns (uint256 btxAmountOut) {
(uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(TARGET_POOL).slot0();
uint256 ratioX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
address token0 = IUniswapV3Pool(TARGET_POOL).token0();
if (token0 == address(USDT)) {
btxAmountOut = Math.mulDiv(usdtAmountIn, ratioX192, 1 << 192);
} else {
btxAmountOut = Math.mulDiv(usdtAmountIn, 1 << 192, ratioX192);
}
}
slot0().sqrtPriceX96 is the current tick's spot, freely moved by a same-block dump. No observe(), no max-change, no second pool.
2. Slippage check is computed from the same quote#
uint256 requiredBTX = _calculateQuote(usdtAmountToMint);
// ...
uint256 amount1Min = (requiredBTX * 97) / 100;
POSITION_MANAGER.mint(MintParams({
amount0Desired: usdtAmountToMint,
amount1Desired: requiredBTX,
amount0Min: (usdtAmountToMint * 97) / 100,
amount1Min: amount1Min,
// ...
}));
amount1Min is 97% of the already crashed requiredBTX. It rejects a further 3% move during mint, not the flash dump that set the quote.
LiquidityVestingConvertOnce is the same oracle (_calculateQuote / _executeMint).
Root cause#
- Single-source spot oracle. One V3 pool's
slot0is the only price. - Same-block manipulability. A dump in the same transaction moves
sqrtPriceX96beforedeposit(). - Protocol-owned inventory.
requiredBTXis pulled from the converter's BTX balance (InsufficientReserveBTXif short) and locked into an LP the attacker can then trade against. - 75/25 split does not help. Treasury takes 75% of USDT; the remaining 25% still sizes a BTX mint from the manipulated quote.
Preconditions#
- Converter holds a large BTX reserve (pre-attack: 1.240M on unlimited, 1.832M on one-time).
- Attacker can flash BTX (Infinity vault held ~25.3M BTX) and USDT (Moolah held ~2.59M USDT).
depositis unpaused and notonlyOwner.- One-time converter still accepts the attacker's address (
hasDepositedfalse).
Attack walkthrough#
Live tx 120873720 (constructor + nested run()):
- Deploy exploit; flash USDT from Moolah (
flashLoan). - Flash ~6M BTX from Pancake Infinity vault / CL (
0xa0ff…/ vault0x238a…). - Dump BTX on the 0.01% oracle pool and the 0.30% pool —
slot0crashes. LiquidityVestingConvert.deposit(10_000e18)andLiquidityVestingConvertOnce.deposit(2_000e18).- Reverse swaps; repay flashes; 63,704.84 USDT on the EOA.
Teaching PoC (fork 120873719, 150k BTX + 15k USDT seed):
| Step | Observation |
|---|---|
| Dump into 0.01% pool | tick 38179 → 39979 |
deposit(10_000) unlimited | BTX 1,240,403 → 1,104,209 |
deposit(2_000) one-time | BTX 1,832,090 → 1,804,851 |
| Total drained | 163,432.97 BTX |
The live 6M dump moved the book far enough to pull ~3.07M BTX (Defimon) / 2.98M BTX (SlowMist).
Diagrams#
Remediation#
- TWAP from
IUniswapV3Pool.observe(or Pancake V3 equivalent) over many minutes; rejectslot0as a standalone mint oracle. - Deviation cap vs a second venue (0.30% pool, CEX/Chainlink if any).
- Hard cap on BTX spent per
deposit(absolute and vs TWAP). - Size LP from a fixed BTX/USDT ratio set by governance, not a flash-loanable tick.
- Pause
depositon abnormalslot0moves; keepamount1Minrelative to a manipulation-resistant quote, not the sameslot0read.
How to reproduce#
cd audits/evm-hack-registry
_shared/run_poc.sh 2026-09-BeatXswapVestingOracle_exp -vvvvv
# expect [PASS] testExploit — 163,432.97 BTX drained from the two converters
Fork: BSC 120873719. Online: BSC_RPC_URL / https://bsc-mainnet.public.blastapi.io.
Reference: https://x.com/SlowMist_Team/status/2098314846765015062 · https://x.com/DefimonAlerts/status/2098273394261102968
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 2026-09-BeatXswapVestingOracle_exp (evm-hack-registry mirror).
- Attack transaction: view on explorer.
Alerts & third-party analyses
- Original alert / thread: post on X.
- Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
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.