TON AMM Router/Pool (WIP)
Smart contract security audit by TierZero · 2025-06-15
Independent research. This is an unsolicited security analysis published by TierZero — not a commissioned audit, and no certificate is issued. It reflects our own review of public code and on-chain events.
Findings summary
Independent research — not a commissioned audit.
Scope & motivation
TierZero is running an internal research track on TON-based AMM router and pool contracts, focused narrowly on message-flow correctness: how a router coordinates a multi-hop swap across several pool contracts, and how it handles the failure paths TON's architecture makes possible but that EVM auditors rarely have to reason about.
We picked this angle because TON's actor model is a genuinely different threat surface. On EVM, a router's swapExactTokensForTokens is one synchronous call stack — if any hop reverts, the whole transaction reverts and state is atomic. On TON, a router sends an internal message to pool A, which (if successful) sends a message to pool B, which sends a message back to the user's jetton wallet. Each hop is a separate transaction, processed in a separate block, with its own gas budget, its own compute phase, and its own chance to fail independently of the others. There is no all-or-nothing rollback across the chain. That property alone has produced a distinct, recurring bug class in TON DeFi over the past two years — stuck funds, double-credits, and mis-accounted balances that trace back to how a contract set (or forgot to set) the bounce flag, or to what it assumed would happen when a downstream hop threw.
This work sits upstream of any specific customer engagement. We are publishing the scope and our working checklist now, before conclusions, because the mechanism is generically interesting and we want the trail auditable. If your protocol runs router/pool logic on TON and wants a paired-eyes review, this is the same lens we'd apply in a commissioned engagement — see our smart contract audit service for how we scope that work, or our code review and audit offering for a lighter-weight pass over router logic specifically.
What we are checking
Concretely, we are working through four questions against router/pool implementations we can read (open-sourced code and decompiled/verified contracts where source isn't public):
- Bounce flag correctness on every outbound message. Does the router set
bounce = trueon messages where a downstream failure must return value and control to a recoverable state, andbounce = falseonly where that's genuinely safe (e.g., final payout legs where a stuck message is preferable to a silent loss)? - What the contract actually does on receipt of a bounced message. A bounced message arrives with the original op-code XORed with
0xFFFFFFFFand a truncated body (typically just the first 32 bits of the original payload survive). If the handler doesn't explicitly branch on the bounced prefix, it either falls through into unrelated logic or throws — and either way, whatever accounting update the router made optimistically before sending the downstream message is now wrong. - Jetton transfer notification authentication. TON's jetton standard has the pool receive a
transfer_notificationmessage from what should be its own jetton wallet. If the pool doesn't recompute and check that the sender address matches the deterministic wallet address (owner + minter + code), a forged notification from an attacker-deployed contract can look like a genuine deposit. - Gas/fee provisioning across hops. Each hop needs enough attached TON to cover forward fees plus the next contract's compute and storage costs. Underestimating this mid-chain doesn't fail cleanly — it can leave a partially executed swap with value stranded in an intermediate contract.
A simplified sketch of the failure mode we're most focused on:
// router forwards hop 1, optimistically marks the swap "in flight"
send_message(pool_a, op::swap, amount_in, bounce = true);
state.pending[order_id] = amount_in; // <-- written before we know pool_a succeeded
// ... later, in the router's message handler ...
if (op == (original_op ^ 0xFFFFFFFF)) {
// this IS the bounce case — did we get here?
// if not handled, state.pending[order_id] never clears
// and the user's principal is now unaccounted for
}
The bug isn't that bouncing exists — it's whether every state write made in anticipation of success has a symmetric, tested rollback on the bounce path, and whether that rollback path is reachable at all given the op-code the handler is actually switching on.
Preliminary observations
These are notes from an in-progress review, not conclusions, and we are not naming a specific deployed protocol at this stage.
- Router implementations we've read vary in how disciplined they are about (1); some set
bounce = trueuniformly by default rather than deciding per message, which is safer but not obviously correct for every leg (medium — worth tracing case by case rather than assuming). - Bounce-handler code paths are, unsurprisingly, the least-tested branches we've seen in available test suites — several repos have no test that actually simulates a mid-chain pool failure and inspects post-bounce state (low).
- Jetton wallet address verification is inconsistent in helper libraries versus what's implemented inline in pool contracts; the correct pattern is well documented by the TON jetton standard, but "well documented" and "consistently applied" aren't the same thing (low).
- We have not yet found, and are not claiming, an exploitable instance of any of the above in live, deployed code — this is checklist-building against a known bug class, not a disclosure.
Next steps
We're extending the review to gas/fee provisioning across three-plus-hop routes, and building a small harness that replays bounce scenarios against testnet deployments to check state consistency mechanically rather than by code reading alone. We'll update this page as the research matures, and will follow standard disclosure practice — private notification first — if anything crosses from "checklist item" to "confirmed issue" in a specific deployed contract. For background on why cross-contract call boundaries are a recurring audit focus across chains, see our writeups on cross-program invocation risk in Solana DeFi and our Solana program security audit checklist for trading systems — the specific mechanics differ, but the underlying discipline (never assume a downstream call succeeded until you've handled the case where it didn't) is the same one we're applying here.
If you're shipping a router or pool on TON and want this kind of message-flow review applied to your specific contracts, our smart contract audit service covers exactly this scope.
Need your contracts audited?
Manual review + tooling across TON, Solana and EVM — certificate and full report included.
Get an audit