## 6. Vulnerability Checklist (3 Patterns)

### 6.1 INTEGER AS BOOLEAN ⚠️ HIGH

**Description**: FunC uses integers for boolean values (0 = false, -1 = true). The bitwise NOT operator `~` on non-standard boolean values (positive integers) produces unexpected results, causing logic errors.

**Background**:
- FunC `true` = -1 (all bits set: `0xFFFFFFFF...`)
- FunC `false` = 0 (all bits clear: `0x00000000...`)
- `~` is bitwise NOT: `~0 = -1`, `~(-1) = 0`
- But `~1 = -2` (not 0!), `~2 = -3` (not 0!)

**Detection Patterns**:
```func
;; VULNERABLE: Using positive integers as booleans
int is_active = 1;  ;; WRONG: Should be -1 for true, 0 for false

if (is_active) {
    ;; This works - 1 is truthy
}

if (~ is_active) {
    ;; PROBLEM: ~1 = -2, which is still truthy!
    ;; This branch will ALWAYS execute, not just when is_active is false
}

;; VULNERABLE: Returning positive integers as booleans
int is_valid(int value) {
    if (value > 100) {
        return 1;  ;; WRONG: Should return -1
    }
    return 0;  ;; Correct for false
}

int valid = is_valid(150);  ;; Returns 1
if (~ valid) {
    ;; PROBLEM: ~1 = -2 (truthy), this executes when it shouldn't!
}

;; VULNERABLE: Boolean arithmetic
int flag1 = 1;  ;; Wrong true value
int flag2 = 1;  ;; Wrong true value
int both_true = flag1 & flag2;  ;; 1 & 1 = 1 (works)
int neither_true = (~ flag1) & (~ flag2);  ;; ~1 & ~1 = -2 & -2 = -2 (WRONG!)
;; Expected 0 (false), got -2 (truthy)
```

**What to Check**:
- [ ] All boolean values use 0 (false) or -1 (true)
- [ ] NO positive integers (1, 2, etc.) used as booleans
- [ ] Functions returning booleans return -1 (not 1) for true
- [ ] Boolean logic with `~`, `&`, `|` uses correct values
- [ ] Conditions test against 0 explicitly where needed

**Mitigation**:
```func
;; SECURE: Use correct boolean values
const int TRUE = -1;   ;; All bits set
const int FALSE = 0;   ;; All bits clear

int is_active = TRUE;  ;; Correct

if (is_active) {
    ;; Works correctly
}

if (~ is_active) {
    ;; Works correctly: ~(-1) = 0 (falsy)
}

;; SECURE: Return correct boolean values
int is_valid(int value) method_id {
    if (value > 100) {
        return TRUE;   ;; -1 for true
    }
    return FALSE;      ;; 0 for false
}

int valid = is_valid(150);
if (~ valid) {
    ;; Correct: ~(-1) = 0 (falsy), this doesn't execute
}

;; SECURE: Boolean operations with correct values
int flag1 = TRUE;   ;; -1
int flag2 = TRUE;   ;; -1
int both_true = flag1 & flag2;         ;; -1 & -1 = -1 (TRUE)
int neither_true = (~ flag1) & (~ flag2);  ;; 0 & 0 = 0 (FALSE)

;; SECURE: Explicit comparisons when needed
int status_code = get_status();  ;; Returns 0, 1, 2, etc.

;; Instead of treating as boolean:
if (status_code) { }  ;; Ambiguous!

;; Explicitly compare:
if (status_code != 0) { }  ;; Clear intent
if (status_code == 1) { }  ;; Even better
```

**Common Mistake Patterns**:
```func
;; MISTAKE 1: Loading boolean from storage/message
slice cs = get_data().begin_parse();
int flag = cs~load_uint(1);  ;; Returns 0 or 1, not 0 or -1!

;; FIX: Convert to proper boolean
int flag_bool = flag ? TRUE : FALSE;

;; MISTAKE 2: Comparing with 1 instead of TRUE
int is_owner = sender == owner_address;  ;; Returns 0 or -1 (correct)

if (is_owner == 1) {  ;; WRONG: will never match
    ;; This never executes!
}

;; FIX: Compare with TRUE or just use directly
if (is_owner == TRUE) { }  ;; Correct
if (is_owner) { }          ;; Also correct

;; MISTAKE 3: Returning count as boolean
int count_items() {
    return items.length;  ;; Returns 0, 1, 2, 3... (not boolean!)
}

int has_items = count_items();
if (~ has_items) {
    ;; WRONG: ~1 = -2 (truthy), ~2 = -3 (truthy), etc.
}

;; FIX: Return proper boolean or use explicit comparison
int has_items() {
    return items.length > 0 ? TRUE : FALSE;
}
;; OR
int count = count_items();
if (count == 0) { }  ;; Explicit comparison
```

**Testing**:
```func
;; Test boolean logic
int test_boolean_logic() {
    int t = TRUE;
    int f = FALSE;

    ;; Basic logic
    throw_unless(100, t == -1);
    throw_unless(101, f == 0);

    ;; Negation
    throw_unless(102, ~t == f);
    throw_unless(103, ~f == t);

    ;; AND logic
    throw_unless(104, t & t == t);
    throw_unless(105, t & f == f);
    throw_unless(106, f & f == f);

    ;; OR logic
    throw_unless(107, t | t == t);
    throw_unless(108, t | f == t);
    throw_unless(109, f | f == f);

    return TRUE;
}
```

**References**: building-secure-contracts/not-so-smart-contracts/ton/integer_as_boolean

---

### 4.2 FAKE JETTON CONTRACT ⚠️ CRITICAL

**Description**: The `transfer_notification` operation can be sent by any contract. Without sender validation, attackers can send fake transfer notifications claiming to have transferred tokens that were never sent.

**Background**:
- Jetton (TON's token standard) uses `transfer_notification` to notify recipients
- Real flow: User → Jetton Wallet → Receiver (with notification)
- Attack: Attacker → Receiver (fake notification, no Jetton Wallet involved)

**Detection Patterns**:
```func
;; VULNERABLE: No sender validation in transfer_notification
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    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) {
        ;; WRONG: No validation of sender_address!
        int jetton_amount = in_msg_body~load_coins();
        slice from_user = in_msg_body~load_msg_addr();
        slice forward_payload = in_msg_body;

        ;; Process as if jettons were received
        ;; Attacker can claim any jetton_amount without actually sending tokens!
        credit_user(from_user, jetton_amount);
    }
}

;; VULNERABLE: Validating user address but not Jetton wallet
if (op == op::transfer_notification) {
    int jetton_amount = in_msg_body~load_coins();
    slice from_user = in_msg_body~load_msg_addr();

    ;; Validates from_user but not sender!
    throw_unless(error::unauthorized, equal_slices(from_user, authorized_user));

    ;; WRONG: Anyone can send this message claiming to be from authorized_user
    credit_user(from_user, jetton_amount);
}

;; VULNERABLE: Trusting forward_payload data
if (op == op::transfer_notification) {
    int jetton_amount = in_msg_body~load_coins();
    slice from_user = in_msg_body~load_msg_addr();
    slice forward_payload = in_msg_body;

    ;; Parse data from forward_payload
    int token_id = forward_payload~load_uint(32);

    ;; WRONG: Attacker controls all this data!
    ;; Can claim any token_id, any jetton_amount
}
```

**What to Check**:
- [ ] `transfer_notification` handler validates sender address
- [ ] Sender must be expected Jetton wallet address
- [ ] Jetton wallet addresses stored during initialization
- [ ] Cannot trust forward_payload without sender validation
- [ ] User address in notification is NOT sufficient validation

**Mitigation**:
```func
;; SECURE: Store expected Jetton wallet address at initialization
global slice jetton_wallet_address;

() load_data() impure {
    slice ds = get_data().begin_parse();
    jetton_wallet_address = ds~load_msg_addr();
    ;; Load other data
}

() save_data() impure {
    set_data(begin_cell()
        .store_slice(jetton_wallet_address)
        ;; Store other data
        .end_cell());
}

;; Initialize with Jetton wallet address
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    load_data();

    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 is expected Jetton wallet
        throw_unless(error::wrong_jetton_wallet,
            equal_slices(sender_address, jetton_wallet_address));

        ;; Now safe to trust the notification
        int jetton_amount = in_msg_body~load_coins();
        slice from_user = in_msg_body~load_msg_addr();
        slice forward_payload = in_msg_body;

        ;; Can safely credit user
        credit_user(from_user, jetton_amount);

        ;; Can safely parse forward_payload
        if (~ forward_payload.slice_empty?()) {
            int token_id = forward_payload~load_uint(32);
            ;; Use token_id
        }
    }
}

;; SECURE: Multiple Jetton support with dictionary
global cell jetton_wallets;  ;; Dictionary: jetton_type -> wallet_address

() load_data() impure {
    slice ds = get_data().begin_parse();
    jetton_wallets = ds~load_dict();
}

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    load_data();

    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) {
        int jetton_amount = in_msg_body~load_coins();
        slice from_user = in_msg_body~load_msg_addr();
        slice forward_payload = in_msg_body;

        ;; Parse jetton type from forward_payload
        int jetton_type = forward_payload~load_uint(8);

        ;; Look up expected wallet address for this jetton type
        (slice expected_wallet, int found) = jetton_wallets.udict_get?(256, jetton_type);

        ;; Validate sender matches expected wallet
        throw_unless(error::unauthorized_jetton,
            found & equal_slices(sender_address, expected_wallet));

        ;; Safe to process
        credit_user_jetton(from_user, jetton_type, jetton_amount);
    }
}
```

**Admin Function to Set Jetton Wallet**:
```func
;; Only owner can set/update Jetton wallet address
if (op == op::set_jetton_wallet) {
    throw_unless(error::unauthorized, equal_slices(sender_address, owner_address));

    slice new_jetton_wallet = in_msg_body~load_msg_addr();
    jetton_wallet_address = new_jetton_wallet;

    save_data();
    return ();
}
```

**Testing**:
```typescript
// Test fake transfer notification is rejected
it("should reject fake transfer notification", async () => {
  const attacker = await blockchain.treasury("attacker");

  // Attacker sends fake transfer_notification directly
  const result = await contract.sendInternalMessage(attacker.getSender(), {
    op: OP_CODES.TRANSFER_NOTIFICATION,
    jettonAmount: toNano("1000"),
    fromUser: user.address,
  });

  expect(result.transactions).toHaveTransaction({
    from: attacker.address,
    to: contract.address,
    success: false, // Should be rejected
    exitCode: ERROR_CODES.WRONG_JETTON_WALLET,
  });
});

// Test real Jetton wallet notification is accepted
it("should accept real jetton transfer", async () => {
  // Send from actual Jetton wallet
  const result = await contract.sendInternalMessage(jettonWallet.address, {
    op: OP_CODES.TRANSFER_NOTIFICATION,
    jettonAmount: toNano("100"),
    fromUser: user.address,
  });

  expect(result.transactions).toHaveTransaction({
    from: jettonWallet.address,
    to: contract.address,
    success: true,
  });
});
```

**References**: building-secure-contracts/not-so-smart-contracts/ton/fake_jetton_contract

---

### 4.3 FORWARD TON WITHOUT GAS CHECK ⚠️ HIGH

**Description**: Allowing users to specify `forward_ton_amount` in outgoing messages without validating sufficient gas can drain the contract's TON balance. User pays small gas but specifies large forward amount from contract balance.

**Detection Patterns**:
```func
;; VULNERABLE: User-specified forward_ton_amount without validation
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    int op = in_msg_body~load_uint(32);

    if (op == op::transfer) {
        slice to_address = in_msg_body~load_msg_addr();
        int amount = in_msg_body~load_coins();
        int forward_ton_amount = in_msg_body~load_coins();  ;; USER CONTROLLED!

        ;; WRONG: No check that msg_value covers forward_ton_amount
        ;; Contract pays from its own balance!

        var msg = begin_cell()
            .store_uint(0x18, 6)
            .store_slice(to_address)
            .store_coins(forward_ton_amount)  ;; Drains contract balance!
            .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
            .end_cell();

        send_raw_message(msg, 1);
    }
}

;; VULNERABLE: No gas validation for operations
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    int op = in_msg_body~load_uint(32);

    if (op == op::claim_reward) {
        slice user = in_msg_body~load_msg_addr();
        int forward_amount = in_msg_body~load_coins();

        ;; Calculate reward
        int reward = calculate_reward(user);

        ;; WRONG: Sends user-specified forward_amount
        ;; No validation that msg_value >= tx_fee + forward_amount
        var msg = begin_cell()
            .store_uint(0x18, 6)
            .store_slice(user)
            .store_coins(forward_amount + reward)
            .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
            .end_cell();

        send_raw_message(msg, 1);  ;; Contract pays gas!
    }
}
```

**What to Check**:
- [ ] User cannot specify arbitrary forward TON amounts
- [ ] IF forward amount is user-specified: validate `msg_value >= tx_fee + forward_ton_amount`
- [ ] Prefer fixed/bounded forward amounts
- [ ] Contract balance protected from drainage
- [ ] Gas costs accounted for in all operations

**Mitigation**:
```func
;; SECURE: Fixed forward amounts (PREFERRED)
const int FORWARD_TON_AMOUNT = 50000000;  ;; 0.05 TON (fixed)
const int TX_FEE = 10000000;  ;; 0.01 TON estimated fee

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    int op = in_msg_body~load_uint(32);

    if (op == op::transfer) {
        slice to_address = in_msg_body~load_msg_addr();
        int amount = in_msg_body~load_coins();

        ;; Use fixed forward amount
        ;; No user control, no drainage risk

        var msg = begin_cell()
            .store_uint(0x18, 6)
            .store_slice(to_address)
            .store_coins(FORWARD_TON_AMOUNT)  ;; Fixed amount
            .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
            ;; Store message body
            .end_cell();

        send_raw_message(msg, 1);
    }
}

;; SECURE: Validate msg_value covers all costs (if user-specified)
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    int op = in_msg_body~load_uint(32);

    if (op == op::transfer_with_forward) {
        slice to_address = in_msg_body~load_msg_addr();
        int amount = in_msg_body~load_coins();
        int forward_ton_amount = in_msg_body~load_coins();

        ;; CRITICAL: Validate msg_value covers tx fee + forward amount
        throw_unless(error::insufficient_gas,
            msg_value >= TX_FEE + forward_ton_amount);

        var msg = begin_cell()
            .store_uint(0x18, 6)
            .store_slice(to_address)
            .store_coins(forward_ton_amount)
            .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
            .end_cell();

        ;; Safe: user provided sufficient gas
        send_raw_message(msg, 1);
    }
}

;; SECURE: Bounded forward amounts
const int MAX_FORWARD_TON = 100000000;  ;; 0.1 TON maximum

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    int op = in_msg_body~load_uint(32);

    if (op == op::claim_with_notification) {
        slice user = in_msg_body~load_msg_addr();
        int forward_ton_amount = in_msg_body~load_coins();

        ;; Enforce maximum forward amount
        throw_unless(error::forward_amount_too_high,
            forward_ton_amount <= MAX_FORWARD_TON);

        ;; Validate msg_value covers costs
        throw_unless(error::insufficient_gas,
            msg_value >= TX_FEE + forward_ton_amount);

        ;; Calculate reward from contract logic
        int reward = calculate_reward(user);

        var msg = begin_cell()
            .store_uint(0x18, 6)
            .store_slice(user)
            .store_coins(reward)  ;; Reward from contract
            .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
            ;; Message body
            .end_cell();

        ;; Send with user's gas
        send_raw_message(msg, 64);  ;; Flag 64: use all remaining gas from incoming message
    }
}

;; SECURE: Don't allow user to specify forward amount at all
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    int op = in_msg_body~load_uint(32);

    if (op == op::withdraw) {
        slice user = in_msg_body~load_msg_addr();

        ;; No forward_ton_amount parameter
        ;; Use contract's calculated amount only

        int withdrawal_amount = calculate_withdrawal(user);

        var msg = begin_cell()
            .store_uint(0x18, 6)
            .store_slice(user)
            .store_coins(withdrawal_amount)  ;; Contract controlled
            .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
            .end_cell();

        send_raw_message(msg, 1);
    }
}
```

**Send Message Flags Reference**:
```func
;; send_raw_message flag values:
;; 0   - Normal send, pay fees from message value
;; 1   - Pay fees separately from contract balance
;; 64  - Return remaining value from incoming message
;; 128 - Carry all remaining balance

;; Safe patterns:
send_raw_message(msg, 64);  ;; Use incoming msg_value for fees
send_raw_message(msg, 0);   ;; Fees from message value itself

;; Dangerous with user input:
send_raw_message(msg, 1);   ;; Fees from contract - validate msg_value!
send_raw_message(msg, 128); ;; Never use with user-controlled amounts!
```

**Testing**:
```typescript
// Test cannot drain contract with large forward amount
it("should reject large forward amount without sufficient gas", async () => {
  const result = await contract.sendInternalMessage(user.getSender(), {
    value: toNano("0.01"), // Only 0.01 TON provided
    body: {
      op: OP_CODES.TRANSFER,
      toAddress: recipient.address,
      amount: toNano("100"),
      forwardTonAmount: toNano("10"), // Trying to forward 10 TON!
    },
  });

  expect(result.transactions).toHaveTransaction({
    success: false,
    exitCode: ERROR_CODES.INSUFFICIENT_GAS,
  });

  // Contract balance should not decrease
  expect(await contract.getBalance()).toEqual(initialBalance);
});
```

**References**: building-secure-contracts/not-so-smart-contracts/ton/forward_value_without_check

---
