Systematically scan TON blockchain smart contracts written in FunC for platform-specific security vulnerabilities related to boolean logic, Jetton token handling, and gas management. This skill encodes 3 critical vulnerability patterns unique to TON's architecture.
.fc, .func
;; FunC contract indicators
#include "imports/stdlib.fc";
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
;; Contract logic
}
() recv_external(slice in_msg) impure {
;; External message handler
}
;; Common patterns
send_raw_message()
load_uint(), load_msg_addr(), load_coins()
begin_cell(), end_cell(), store_*()
transfer_notification operation
op::transfer, op::transfer_notification
.store_uint().store_slice().store_coins()
contracts/*.fc - FunC contract sourcewrappers/*.ts - TypeScript wrapperstests/*.spec.ts - Contract testston.config.ts or wasm.config.ts - TON project configWhen invoked, I will:
When vulnerabilities are found, you'll get a report like this:
=== TON VULNERABILITY SCAN RESULTS ===
Project: my-ton-contract
Files Scanned: 3 (.fc, .tact)
Vulnerabilities Found: 2
---
[CRITICAL] Missing Replay Protection
File: contracts/wallet.fc:45
Pattern: No sequence number or nonce validation
---
## 5. Vulnerability Patterns (3 Patterns)
I check for 3 critical vulnerability patterns unique to TON. For detailed detection patterns, code examples, mitigations, and testing strategies, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
### Pattern Summary:
1. **Missing Sender Check** ⚠️ CRITICAL - No sender validation on privileged operations
2. **Integer Overflow** ⚠️ CRITICAL - Unchecked arithmetic in FunC
3. **Improper Gas Handling** ⚠️ HIGH - Insufficient gas reservations
For complete vulnerability patterns with code examples, see [VULNERABILITY_PATTERNS.md](resources/VULNERABILITY_PATTERNS.md).
## 5. Scanning Workflow
### Step 1: Platform Identification
1. Verify FunC language (`.fc` or `.func` files)
2. Check for TON Blueprint or toncli project structure
3. Locate contract source files
4. Identify Jetton-related contracts
### Step 2: Boolean Logic Review
```bash
# Find boolean-like variables
rg "int.*is_|int.*has_|int.*flag|int.*enabled" contracts/
# Check for positive integers used as booleans
rg "= 1;|return 1;" contracts/ | grep -E "is_|has_|flag|enabled|valid"
# Look for NOT operations on boolean-like values
rg "~.*\(|~ " contracts/
For each boolean:
# Find transfer_notification handlers
rg "transfer_notification|op::transfer_notification" contracts/
For each Jetton handler:
# Find forward amount usage
rg "forward_ton_amount|forward_amount" contracts/
rg "load_coins\(\)" contracts/
# Find send_raw_message calls
rg "send_raw_message" contracts/
For each outgoing message:
TON contracts require thorough manual review:
~, &, | operators## [CRITICAL] Fake Jetton Contract - Missing Sender Validation
**Location**: `contracts/staking.fc:85-95` (recv_internal, transfer_notification handler)
**Description**:
The `transfer_notification` operation handler does not validate that the sender is the expected Jetton wallet contract. Any attacker can send a fake `transfer_notification` message claiming to have transferred tokens, crediting themselves without actually depositing any Jettons.
**Vulnerable Code**:
```func
// staking.fc, line 85
if (op == op::transfer_notification) {
int jetton_amount = in_msg_body~load_coins();
slice from_user = in_msg_body~load_msg_addr();
;; WRONG: No validation of sender_address!
;; Attacker can claim any jetton_amount
credit_user(from_user, jetton_amount);
}
Attack Scenario:
transfer_notification message to staking contractProof of Concept:
// Attacker sends fake transfer_notification
const attackerContract = await blockchain.treasury("attacker");
await stakingContract.sendInternalMessage(attackerContract.getSender(), {
op: OP_CODES.TRANSFER_NOTIFICATION,
jettonAmount: toNano("1000000"), // Fake amount
fromUser: attackerContract.address,
});
// Attacker successfully credited without sending real Jettons
const balance = await stakingContract.getUserBalance(attackerContract.address);
expect(balance).toEqual(toNano("1000000")); // Attack succeeded
Recommendation: Store expected Jetton wallet address and validate sender:
global slice jetton_wallet_address;
() recv_internal(...) impure {
load_data(); ;; Load jetton_wallet_address from storage
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
if (op == op::transfer_notification) {
;; CRITICAL: Validate sender
throw_unless(error::wrong_jetton_wallet,
equal_slices(sender_address, jetton_wallet_address));
int jetton_amount = in_msg_body~load_coins();
slice from_user = in_msg_body~load_msg_addr();
;; Safe to credit user
credit_user(from_user, jetton_amount);
}
}
References:
---
## 7. Priority Guidelines
### Critical (Immediate Fix Required)
- Fake Jetton contract (unauthorized minting/crediting)
### High (Fix Before Launch)
- Integer as boolean (logic errors, broken conditions)
- Forward TON without gas check (balance drainage)
---
## 8. Testing Recommendations
### Unit Tests
```typescript
import { Blockchain } from "@ton/sandbox";
import { toNano } from "ton-core";
describe("Security tests", () => {
let blockchain: Blockchain;
let contract: Contract;
beforeEach(async () => {
blockchain = await Blockchain.create();
contract = blockchain.openContract(await Contract.fromInit());
});
it("should use correct boolean values", async () => {
// Test that TRUE = -1, FALSE = 0
const result = await contract.getFlag();
expect(result).toEqual(-1n); // True
expect(result).not.toEqual(1n); // Not 1!
});
it("should reject fake jetton transfer", async () => {
const attacker = await blockchain.treasury("attacker");
const result = await contract.send(
attacker.getSender(),
{ value: toNano("0.05") },
{
$$type: "TransferNotification",
query_id: 0n,
amount: toNano("1000"),
from: attacker.address,
}
);
expect(result.transactions).toHaveTransaction({
success: false, // Should reject
});
});
it("should validate gas for forward amount", async () => {
const result = await contract.send(
user.getSender(),
{ value: toNano("0.01") }, // Insufficient gas
{
$$type: "Transfer",
to: recipient.address,
forward_ton_amount: toNano("1"), // Trying to forward 1 TON
}
);
expect(result.transactions).toHaveTransaction({
success: false,
});
});
});
// Test with real Jetton wallet
it("should accept transfer from real jetton wallet", async () => {
// Deploy actual Jetton minter and wallet
const jettonMinter = await blockchain.openContract(JettonMinter.create());
const userJettonWallet = await jettonMinter.getWalletAddress(user.address);
// Set jetton wallet in contract
await contract.setJettonWallet(userJettonWallet);
// Real transfer from Jetton wallet
const result = await userJettonWallet.sendTransfer(
user.getSender(),
contract.address,
toNano("100"),
{}
);
expect(result.transactions).toHaveTransaction({
to: contract.address,
success: true,
});
});
building-secure-contracts/not-so-smart-contracts/ton/
Before completing TON contract audit:
Boolean Logic (HIGH):
~, &, | uses correct valuesJetton Security (CRITICAL):
transfer_notification handler validates sender addressGas & Forward Amounts (HIGH):
msg_value >= tx_fee + forward_amount
send_raw_message flags usedTesting: