Trigger Pattern SPL token CPI transfers, token_account.amount, invoke/invoke_signed, Transfer/TransferChecked - Inject Into Lifecycle, External-Env agents
Trigger Pattern: SPL token CPI transfers,
token_account.amount,invoke/invoke_signed,Transfer/TransferCheckedInject Into: Lifecycle, External-Env agents
For every token the protocol handles:
Where can tokens enter?
deposit() / stake() instructions - standard entry points via CPI to Token ProgramFor each entry point:
vault.total_value, pool.total_deposited)token_account.amount read directly for calculations? -> Donation attack vectortoken_account.amount compared anywhere?Red flags:
token_account.amount directlyWhere can tokens leave?
withdraw() / unstake() instructionsFor each exit: does the tracked balance decrease BEFORE or AFTER the CPI transfer? For each transfer call: can the source account be underfunded at execution time? (funds deployed externally, locked, or lent out → transfer reverts)
For each transfer instruction: can the source and destination token accounts belong to the same owner/authority, or be the same account? If YES: does a self-transfer update accounting state (fees credited, rewards claimed, snapshots updated, share ratios changed) without net token movement? Flag as FINDING. Note: this check targets accounting manipulation from self-transfers, distinct from the account key uniqueness validation in ACCOUNT_VALIDATION.md's Self-Transfer Risk column.
For protocols handling multiple token types:
Check: If instruction A handles MintX and instruction B handles MintY, can MintX accounts be passed to instruction B's logic?
Can tokens be sent to the protocol's token accounts without calling deposit()?
If YES (almost always YES on Solana):
token_account.amount)If NO:
For EVERY token type the protocol holds, queries, or receives - including SOL lamports:
| Token Type | Can Transfer To Protocol? | Changes Accounting? | Blocks Operations? | Triggers Side Effects? |
|---|---|---|---|---|
| SOL (lamports) | YES (always) | YES/NO | YES/NO | YES/NO |
| {spl_token_a} | YES/NO | YES/NO | YES/NO | YES/NO |
RULE: If ANY transferable token affects state -> analyze: accounting divergence, rent impact, operation blocking, side effect chains.
For each token identified:
| Token | Entry Points | Exit Points | Tracking Var | token_account.amount Used? | Unsolicited Possible? |
|---|---|---|---|---|---|
| [Name/Mint] | deposit, CPI return | withdraw, claim | total_deposited | YES/NO | YES/NO |
For protocols with multiple tokens:
For every CPI call that returns tokens or modifies accounts:
Common mismatches:
Check: invoke_signed(&instruction, &[accounts...]) - verify the target program and returned account state match expectations.
For every CPI transfer to external programs:
| Token / Mint | On Transfer Side Effect | Impact on Protocol |
|---|---|---|
| [Token] | TransferHook executes arbitrary CPI | Potential reentrancy / state corruption |
| [Token] | TransferFee withholds portion | Accounting mismatch (sent != received) |
| [Token] | PermanentDelegate can move tokens | Tokens can leave without protocol consent |
| CPI Call / Event | Side Effect | Token Type Produced | Protocol Handles This Type? | Mismatch? |
|---|---|---|---|---|
| {cpi_call} | {side_effect} | {token_type_or_UNKNOWN} | YES/NO | YES/NO |
RULES: Side effect type != expected -> FINDING. Type UNKNOWN -> CONTESTED (Rule 4). Check BOTH CPI calls AND unsolicited transfers.
// RED FLAG: Direct balance usage - donatable
let rate = ctx.accounts.vault_token.amount / vault.total_shares;
// BETTER: Tracked balance - but verify total_deposited updated on ALL entry paths
let rate = vault.total_deposited / vault.total_shares;
When this skill identifies an issue:
**ID**: [TF-N]
**Severity**: [based on fund impact]
**Step Execution**: S1,2,3,4,5,6,7,8,9 | X(reasons) | ?(uncertain)
**Location**: program/src/instructions/file.rs:LineN
**Title**: [Token type] can enter/exit via [path] without [expected accounting update]
**Description**: [Trace the token flow and where it diverges from expected]
**Impact**: [What breaks: exchange rates, user balances, protocol insolvency]
CRITICAL: You MUST report completion status for ALL sections. Findings with incomplete sections will be flagged for depth review.
| Section | Required | Completed? | Notes |
|---|---|---|---|
| 1. Token Entry Points | YES | Y/X/? | |
| 2. Token State Tracking | YES | Y/X/? | |
| 3. Token Exit Points | YES | Y/X/? | |
| 4. Token Type Separation | IF multi-token | Y/X(N/A)/? | |
| 5. Unsolicited Transfer Analysis | YES | Y/X/? | |
| 5b. Unsolicited Transfer Matrix (All Types) | YES | Y/X/? | MANDATORY - never skip |
| 6. Token Flow Checklist | YES | Y/X/? | |
| 7. Cross-Token Interactions | IF multi-token | Y/X(N/A)/? | |
| 8. CPI Return Type Verification | YES | Y/X/? | MANDATORY - never skip |
| 9. Transfer Side Effects (Token-2022) | YES | Y/X/? | MANDATORY - never skip |
| 9d. Side Effect Token Type | YES | Y/X/? | MANDATORY - never skip |
TOKEN_2022_EXTENSIONS.md for TransferHook/TransferFee. IF program ID UNVERIFIED -> mark CONTESTED.Sections 8 and 9 MUST produce tabular output even if uncertain. If UNVERIFIED: verdict cannot be REFUTED, use CONTESTED. If side effects UNKNOWN: apply adversarial default and document assumptions.