Smart Contracts

TON Contract Security: Bounces, Messages, and Code Cells

FunC, Tact, and Tolk; TON's message-passing architecture; bounce handling; StateInit and code-cell mutation; gas attached to messages; the bug classes specific to TON's sharded design.

Red Asgard Research·Updated May 2026

How a TON contract gets compromised

TON is an asynchronous actor system. Contracts do not call each other synchronously. They send messages. Messages can bounce back if the recipient cannot accept them. Each message carries its own gas budget. The runtime is sharded; a contract on one shard messaging a contract on another faces a latency the audit must account for.

Most TON exploits come from this asynchrony. Either the contract assumes a message succeeded before the bounce returned, or it assumes its own state has not been mutated between sending a message and receiving the response, or it sends a message with the wrong gas budget and the recipient cannot process it.

The audit methodology applies to TON the same way it applies to EVM. Read it first if you have not.

What's different from EVM

Three differences shape the audit work.

Asynchronous message passing. A contract that sends a message does not get a synchronous response. The recipient processes the message in a later block (possibly much later if the recipient is on a different shard). The original contract receives the result, if at all, through a separate inbound message.

Bounces. If a recipient cannot process a message (revert, out-of-gas, unrecognized opcode), the message bounces back to the sender as an inbound message. The sender's bounce handler runs. State that was committed before the original send is still committed.

Code cells and StateInit. A TON contract's code lives in a code cell. The code is mutable in the sense that the contract can replace its own code cell during execution. Whoever can trigger that replacement controls the contract.

What we look at first

The languages. TON contracts are written in FunC (low-level, the original), Tact (high-level, the most common new-development choice), or Tolk (TON Studio's newer language, currently rolling out alongside Tact). The languages compile to TVM (TON Virtual Machine) code cells. The audit work depends on which language the contract is written in; FunC requires more manual care than Tact, and Tolk audits today still rely heavily on FunC and Tact analogues until the Tolk-specific tooling catches up.

The send-mode patterns. When a contract sends a message, it specifies a send mode (separate gas, carry remaining balance, ignore errors, etc.). The send mode determines what happens on error. A contract that uses SEND_MODE_IGNORE_ERRORS for a critical message is masking exploitable failures.

The bounce-handling logic. Every contract that sends bounceable messages should handle bounces. A contract that does not is treating bounces as silent successes.

The StateInit and code-replacement paths. Functions that call set_code or generate new StateInit are upgrade primitives. The audit-time check is who can trigger them and under what governance.

The pattern catalog

Bounce-not-handled. A contract sends a bounceable message and updates its own state to assume the message will succeed. The message fails; the bounce returns; the bounce handler is missing or incomplete; the contract's state is now inconsistent with reality.

Send-mode misuse. SEND_MODE_CARRY_ALL_REMAINING_BALANCE sends a message with all remaining gas. A contract that uses this on a path where it expects to do further work after the send will fail. SEND_MODE_IGNORE_ERRORS swallows errors that could indicate a real problem.

Message replay. TON does not have EVM-style nonces. A contract that accepts signed messages must include its own replay protection (sequence numbers, expiration timestamps, or signed-once tokens). Without this, an attacker can replay a previously-valid signed message.

Gas-attached-to-message manipulation. A contract that pays for a downstream message's gas can be drained if the recipient consumes more than expected. A contract that under-pays a downstream message's gas causes the recipient to fail and bounce. The audit-time check is whether gas budgets are sized correctly against worst-case downstream behavior.

Get-method vs internal-message boundary. Get methods are read-only and can be called by anyone off-chain. Internal messages are the contract-to-contract path that triggers state changes. A function intended as a get method that mutates state is a free-for-all access path. A function intended as an internal message that has the wrong opcode prefix can be triggered through unintended paths.

StateInit forgery. A contract address is derived from its StateInit (initial code + initial data). An attacker who can generate a StateInit that hashes to a target address can pre-deploy a malicious contract at that address. Contracts that assume an address has a particular StateInit (because they computed the address themselves) must verify, not assume.

Sharding-aware addressing. TON addresses encode a shard. A contract that messages across shards faces additional latency and increased risk of message loss. State that depends on cross-shard ordering must include explicit sequencing.

Persistent storage size limits. TON contracts have a maximum storage size. A contract that grows its storage proportional to user actions can hit the limit. The audit-time check is the worst-case storage size against the contract's lifecycle.

Special-purpose contracts (wallets, jettons, etc.). TON's standard contracts (V3R2, V4R2, and V5R1 wallet contracts; jetton masters and jetton wallets) have specific patterns that an audit should compare against. A V3R2 wallet's seqno-based replay protection differs from a V4R2 wallet's plugin model, and from V5R1's gasless-transaction extension hooks. A custom wallet that re-implements wallet logic is a target; we audit against the closest standard variant.

Jetton master/wallet pattern. TON jettons (the fungible-token standard) split state across a master contract (holding total supply, mint authority) and per-holder jetton-wallet contracts (each holding one user's balance). A holder's jetton wallet is itself a contract, derived from the master plus the holder's address. The pattern means a jetton transfer is a message from one jetton wallet to another, not a balance update inside the master. We audit the master's authority paths, the wallet derivation correctness, and the message flow on transfer.

External messages surface. TON contracts can receive external messages from outside the chain (signed off-chain and submitted by anyone). External messages are a separate ingress path with no implicit sender authority. A contract that accepts external messages must enforce its own signature scheme and replay protection. Most TON wallets use this path for user-initiated transactions.

Gas refund mechanism. Outbound messages on TON carry a gas budget. Unused gas is returned to the sender in the form of a refund message. Contracts that depend on gas refunds (for accounting, for fee schemes, or to recoup excess attached gas) must handle the case where the refund is delayed, lost, or arrives after the contract has already mutated state assuming the gas was spent.

What we audit on a TON contract

We audit:

  • every outbound message's send mode and gas budget against worst-case downstream behavior
  • every contract that sends bounceable messages for bounce-handler completeness
  • every signed-message acceptance for replay protection
  • every set_code or StateInit-replacement path for authority and time-lock
  • the get-method vs internal-message boundary on every public function
  • StateInit derivation correctness on every contract that computes addresses
  • cross-shard message paths for latency-dependent assumptions
  • storage growth against the contract lifecycle

The deliverable is the audit report described in our methodology.

Tools that earn their place

The TON ecosystem's tooling is younger than EVM's. The TON emulator and the Tact compiler's compile-time checks are the production tools. Manual review of FunC or Tact source carries more weight than on EVM. Property-based fuzzing and formal verification on TON are emerging; no single production-grade tool dominates yet.

The TON emulator lets the audit run contract code against a simulated runtime. Standard for unit and integration testing.

The Tact compiler does static checks at compile time. Production Tact code inherits the compiler's safety guarantees in ways FunC code does not.

TVM bytecode review. The TON Virtual Machine has its own instruction set and code-cell model. For high-stakes contracts where the source compiler is not the trust boundary (or where the deployed code cell has been verified independently of the source repository), the audit reads the compiled TVM bytecode against the source. Auditors with TVM familiarity are not interchangeable with auditors who only read FunC or Tact source.

What clean means

A clean TON audit means the bounce handling, send-mode choices, gas budgeting, and message-replay protection are sound under the threat model the contract states. It does not mean the TON network's sharding behavior or the broader ecosystem's standards will not change in ways that affect the contract. We name the network-level dependencies explicitly.

Tags
tonfunctactsmart-contractsauditing