How We Found a Critical Vulnerability in Bidask, a TON DEX

A real-world case study of using TSA's drain check to find a critical Bidask pool vulnerability.

How We Found a Critical Vulnerability in Bidask, a TON DEX

In this post, we describe how we found and reproduced a critical vulnerability in Bidask, a DEX on TON. The bug let a pool refund more Gram than it should have, so an attacker could withdraw almost all of the pool's Gram liquidity.

We found the issue with our drain-check analyzer, reproduced the drain on the live pool, and reported it to the Bidask team.

What happened

We ran drain-check on the Bidask pool contract, and it reported that the pool could be drained. We reproduced the drain against the pool's live on-chain state and, as a proof of concept, withdrew roughly 30k Gram — almost all of the pool's Gram liquidity.

Exploit transaction on Tonviewer

We then contacted the Bidask team and returned the funds. They ran an internal audit to locate the source of the problem, fixed it, and paid a bounty for the report. The bug is fixed now.

Bounty transaction on Tonviewer

What TSA found and why the pool was drainable

drain-check answers a single question: can an unauthorized user extract more value from the contract than correct balance accounting should allow? For Bidask pool contract the answer was yes, and the analyzer produced concrete inputs that reproduce the vulnerable execution path. That was enough to confirm the pool was exploitable and to start the disclosure process, before anyone understood the source-level cause.

The pool used a custom reserve routine to decide how much Gram to keep before sending a message with mode 128, which forwards all of the contract's remaining balance. That routine computed the amount to reserve from global variables holding the pool's balances. On one swap-processing path, those global variables were not updated before the reserve call, so the pool reserved against stale values and then sent out more Gram than that logical account actually held. A user who sent a swap message without attaching enough Gram could trigger this path and pull value out of the pool.

The fix was to update those balance variables before the reserve-and-send step.

We have a dedicated tutorial for this analyzer: drain-check guide.

Reproduce it yourself

You can reproduce the same result with the drain-check analyzer in our Blueprint plugin.

The public documentation for the plugin is here.

Add the TSA Blueprint plugin

Inside a Blueprint project, add the plugin:

yarn add blueprint-tsa

Then make sure the plugin is enabled in blueprint.config.ts:

import { TsaPlugin } from 'blueprint-tsa';
 
export const config = {
  plugins: [new TsaPlugin()],
};

Fetch the vulnerable pool contract

Because the live Bidask contract is already fixed, you should first use fetch-contract command to fetch the historical contract state at the timestamp relevant to the exploit:

yarn blueprint tsa fetch-contract \
  --network mainnet \
  --timestamp 1781064280 \
  --address EQAAS3mks4oGKCfJ7VaTLITfqaWC5InGCw00g-b-k70IzLO2 \
  --name BidaskPool

The command saves the contract code and data under tsa/boc and prints the historical balance:

✅ Contract code saved to tsa/boc/BidaskPool.boc
✅ Contract data saved to tsa/boc/BidaskPool.data.boc
Balance: 32606833720467 nanotons

Run the drain check

Then run the drain check with the historical pool balance and storage data:

yarn blueprint tsa drain-check -c BidaskPool --balance 32606833720467 --data tsa/boc/BidaskPool.data.boc

The result below analyzes the fetched pool code, historical balance, and concrete historical storage data, reproducing the drain condition for that historical pool state:

✅ Using contract fetched from the blockchain: tsa/boc/BidaskPool.boc
Contract: BidaskPool
Mode: TON drain
Balance: 32606833720467 nanotons

✅ Analysis complete

⚠️ Vulnerability found!
Summary path: tsa/reports/run-2026-07-13-35083623/summary.txt
Typed input: tsa/reports/run-2026-07-13-35083623/typed-input.yaml
SARIF with full information: tsa/reports/run-2026-07-13-35083623/report.sarif

The exact report directory name changes from run to run, but the important result is ⚠️ Vulnerability found!.

Conclusion

drain-check reasons about value flow, refunds, and balance accounting rather than local code patterns, so it caught a bug that a purely syntactic check would miss. Here it turned a subtle accounting-order mistake into a concrete, reproducible drain — exactly what you need to responsibly disclose an issue and get it fixed. If you build or audit TON contracts, TSA can be a practical part of that security process.