Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:01:25 +08:00
commit d733741f8a
37 changed files with 26647 additions and 0 deletions

View File

@@ -0,0 +1,325 @@
---
name: solana-security
description: Audit Solana programs (Anchor or native Rust) for security vulnerabilities. Use when reviewing smart contract security, finding exploits, analyzing attack vectors, performing security assessments, or when explicitly asked to audit, review security, check for bugs, or find vulnerabilities in Solana programs.
---
# Solana Security Auditing
Systematic security review framework for Solana programs, supporting both Anchor and native Rust implementations.
## Review Process
Follow this systematic 5-step process for comprehensive security audits:
### Step 1: Initial Assessment
Understand the program's context and structure:
- **Framework**: Anchor vs Native Rust (check for `use anchor_lang::prelude::*`)
- **Anchor version**: Check `Cargo.toml` for compatibility and known issues
- **Dependencies**: Oracles (Pyth, Switchboard), external programs, token programs
- **Program structure**: Count instructions, identify account types, analyze state management
- **Complexity**: Lines of code, instruction count, PDA patterns
- **Purpose**: DeFi, NFT, governance, gaming, etc.
### Step 2: Systematic Security Review
For each instruction, perform security checks in this order:
1. **Account Validation** - Verify signer, owner, writable, and initialization checks
2. **Arithmetic Safety** - Check all math operations use `checked_*` methods
3. **PDA Security** - Validate canonical bumps and seed uniqueness
4. **CPI Security** - Ensure cross-program invocations validate target programs
5. **Oracle/External Data** - Verify price staleness and oracle status checks
**→ See [references/security-checklists.md](references/security-checklists.md) for detailed checklists**
### Step 3: Vulnerability Pattern Detection
Scan for common vulnerability patterns:
- Type cosplay attacks
- Account reloading issues
- Improper account closing
- Missing lamports checks
- PDA substitution attacks
- Arbitrary CPI vulnerabilities
- Missing ownership validation
- Integer overflow/underflow
**→ See [references/vulnerability-patterns.md](references/vulnerability-patterns.md) for code examples and exploit scenarios**
### Step 4: Architecture and Testing Review
Evaluate overall design quality:
- PDA design patterns and collision prevention
- Account space allocation and rent exemption
- Error handling approach and coverage
- Event emission for critical state changes
- Compute budget optimization
- Test coverage (unit, integration, fuzz)
- Upgrade strategy and authority management
### Step 5: Generate Security Report
Provide findings using this structure:
**Severity Levels:**
- 🔴 **Critical**: Funds can be stolen/lost, protocol completely broken
- 🟠 **High**: Protocol can be disrupted, partial fund loss possible
- 🟡 **Medium**: Suboptimal behavior, edge cases, griefing attacks
- 🔵 **Low**: Code quality, gas optimization, best practices
- 💡 **Informational**: Recommendations, improvements, documentation
**Finding Format:**
```markdown
## 🔴 [CRITICAL] Title
**Location:** `programs/vault/src/lib.rs:45-52`
**Issue:**
Brief description of the vulnerability
**Vulnerable Code:**
```rust
// Show the problematic code
```
**Exploit Scenario:**
Step-by-step explanation of how this can be exploited
**Recommendation:**
```rust
// Show the secure alternative
```
**References:**
- [Link to relevant documentation or similar exploits]
```
**Report Summary:**
- Total findings by severity
- Critical issues first (prioritize by risk)
- Quick wins (easy fixes with high impact)
- Recommendations for testing improvements
## Quick Reference
### Essential Checks (Every Instruction)
**Anchor:**
```rust
// ✅ Account validation with constraints
#[derive(Accounts)]
pub struct SecureInstruction<'info> {
#[account(
mut,
has_one = authority, // Relationship check
seeds = [b"vault", user.key().as_ref()],
bump, // Canonical bump
)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>, // Signer required
pub token_program: Program<'info, Token>, // Program validation
}
// ✅ Checked arithmetic
let total = balance.checked_add(amount)
.ok_or(ErrorCode::Overflow)?;
```
**Native Rust:**
```rust
// ✅ Manual account validation
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != program_id {
return Err(ProgramError::IllegalOwner);
}
// ✅ Checked arithmetic
let total = balance.checked_add(amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
```
### Critical Anti-Patterns
**Never Do:**
- Use `saturating_*` arithmetic methods (hide errors)
- Use `unwrap()` or `expect()` in production code
- Use `init_if_needed` without additional checks
- Skip signer validation ("they wouldn't call this...")
- Use unchecked arithmetic operations
- Allow arbitrary CPI targets
- Forget to reload accounts after mutations
**Always Do:**
- Use `checked_*` arithmetic (`checked_add`, `checked_sub`, etc.)
- Use `ok_or(error)?` for Option unwrapping
- Use explicit `init` with proper validation
- Require `Signer<'info>` or `is_signer` checks
- Use `Program<'info, T>` for CPI program validation
- Reload accounts after external calls that mutate state
- Validate account ownership, discriminators, and relationships
## Framework-Specific Patterns
### Anchor Security Patterns
**→ See [references/anchor-security.md](references/anchor-security.md) for:**
- Account constraint best practices
- Common Anchor-specific vulnerabilities
- Secure CPI patterns with `CpiContext`
- Event emission and monitoring
- Custom error handling
### Native Rust Security Patterns
**→ See [references/native-security.md](references/native-security.md) for:**
- Manual account validation patterns
- Secure PDA derivation and signing
- Low-level CPI security
- Account discriminator patterns
- Rent exemption validation
## Modern Practices (2025)
- **Use Anchor 0.30+** for latest security features
- **Implement Token-2022** with proper extension handling
- **Use `InitSpace` derive** for automatic space calculation
- **Emit events** for all critical state changes
- **Write fuzz tests** with Trident framework
- **Document invariants** in code comments
- **Follow progressive roadmap**: Dev → Audit → Testnet → Audit → Mainnet
## Security Fundamentals
**→ See [references/security-fundamentals.md](references/security-fundamentals.md) for:**
- Security mindset and threat modeling
- Core validation patterns (signers, owners, mutability)
- Input validation best practices
- State management security
- Arithmetic safety
- Re-entrancy considerations
## Common Vulnerabilities
**→ See [references/vulnerability-patterns.md](references/vulnerability-patterns.md) for:**
- Missing signer validation
- Integer overflow/underflow
- PDA substitution attacks
- Account confusion
- Arbitrary CPI
- Type cosplay
- Improper account closing
- Precision loss in calculations
Each vulnerability includes:
- ❌ Vulnerable code example
- 💥 Exploit scenario
- ✅ Secure alternative
- 📚 References
## Security Checklists
**→ See [references/security-checklists.md](references/security-checklists.md) for:**
- Account validation checklist
- Arithmetic safety checklist
- PDA and account security checklist
- CPI security checklist
- Oracle and external data checklist
- Token integration checklist
## Known Issues and Caveats
**→ See [references/caveats.md](references/caveats.md) for:**
- Solana-specific quirks and gotchas
- Anchor framework limitations
- Testing blind spots
- Common misconceptions
- Version-specific issues
## Security Resources
**→ See [references/resources.md](references/resources.md) for:**
- Official security documentation
- Security courses and tutorials
- Vulnerability databases
- Audit report examples
- Security tools (Trident, fuzzers)
- Security firms and auditors
## Key Questions for Every Audit
Always verify these critical security properties:
1. **Can an attacker substitute accounts?**
- PDA validation, program ID checks, has_one constraints
2. **Can arithmetic overflow or underflow?**
- All math uses checked operations, division by zero protected
3. **Are all accounts properly validated?**
- Owner, signer, writable, initialized checks present
4. **Can the program be drained?**
- Authorization checks, reentrancy protection, account confusion prevention
5. **What happens in edge cases?**
- Zero amounts, max values, closed accounts, expired data
6. **Are external dependencies safe?**
- Oracle validation (staleness, status), CPI targets verified, token program checks
## Audit Workflow
### Before Starting
1. Understand the protocol purpose and mechanics
2. Review documentation and specifications
3. Set up local development environment
4. Run existing tests and check coverage
### During Audit
1. Follow the 5-step review process systematically
2. Document findings with severity and remediation
3. Create proof-of-concept exploits for critical issues
4. Test fixes and verify they work
### After Audit
1. Present findings clearly prioritized by severity
2. Provide actionable remediation steps
3. Re-audit after fixes are implemented
4. Document lessons learned for the protocol
## Testing for Security
Beyond code review, validate security through testing:
- **Unit tests**: Test each instruction's edge cases
- **Integration tests**: Test cross-instruction interactions
- **Fuzz testing**: Use Trident to discover unexpected behaviors
- **Exploit scenarios**: Write POCs for found vulnerabilities
- **Upgrade testing**: Verify migration paths are secure
## Core Principle
**In Solana's account model, attackers can pass arbitrary accounts to any instruction.**
Security requires explicitly validating:
- ✅ Every account's ownership
- ✅ Every account's type (discriminator)
- ✅ Every account's relationships
- ✅ Every account's state
- ✅ Every signer requirement
- ✅ Every arithmetic operation
- ✅ Every external call
There are no implicit guarantees. **Validate everything, trust nothing.**

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,386 @@
# Important Caveats
Critical limitations, quirks, and gotchas in Solana and Anchor development that every security reviewer must know.
## Anchor Framework Limitations
### 1. `init_if_needed` Re-initialization Risk
```rust
// Dangerous: Can bypass initialization logic
#[account(init_if_needed, payer = user, space = ...)]
pub user_account: Account<'info, UserAccount>,
```
**Issue:** If account already exists, initialization is skipped entirely. Existing malicious or inconsistent data is not validated.
**When to use:** Only when you explicitly validate existing accounts in instruction logic.
### 2. `AccountLoader` Missing Discriminator Check
```rust
// Does NOT validate discriminator by default!
#[account(mut)]
pub user: AccountLoader<'info, User>,
```
**Issue:** `AccountLoader` is for zero-copy accounts and doesn't check the account discriminator automatically. Enables type cosplay attacks.
**Solution:** Use `Account<'info, T>` when possible, or add manual discriminator check.
### 3. `close` Constraint Ordering
```rust
// ❌ Wrong: close must be last
#[account(
close = receiver,
mut,
has_one = authority
)]
// ✅ Correct: close is last
#[account(
mut,
has_one = authority,
close = receiver
)]
```
**Issue:** Anchor processes constraints in order. If `close` isn't last, subsequent constraints may check zeroed account.
### 4. Space Calculation Errors Are Permanent
```rust
// If this space is wrong, account is unusable!
#[account(
init,
payer = user,
space = 8 + 32 // Too small = can't deserialize later!
)]
pub user_account: Account<'info, UserAccount>,
```
**Issue:** Once initialized, account size is fixed. Too small = deserialization fails. Too large = wasted rent.
**Solution:** Always use `InitSpace` derive macro:
```rust
#[account]
#[derive(InitSpace)]
pub struct UserAccount {
pub authority: Pubkey,
#[max_len(100)]
pub name: String,
}
// Then use:
space = 8 + UserAccount::INIT_SPACE
```
### 5. `constraint` Expression Limitations
```rust
// constraint expressions can't call functions that return Results!
#[account(
constraint = some_validation(account.value)? @ ErrorCode::Invalid // Compile error!
)]
```
**Issue:** Constraint expressions must be simple boolean checks. Cannot use `?` operator.
**Solution:** Validate in instruction body for complex checks.
## Solana Runtime Quirks
### 1. Account Data Persists After Zeroing Lamports
```rust
// Within same transaction:
**account.lamports.borrow_mut() = 0;
let data = account.try_borrow_data()?; // Still readable!
```
**Issue:** Account data remains accessible within the transaction even after lamports are zeroed. Only garbage collected after transaction completes.
**Implication:** Always check lamports before reading account data.
### 2. Non-Canonical PDA Bumps
```rust
// Multiple PDAs possible with different bumps!
let (pda_255, bump_255) = Pubkey::find_program_address(seeds, program_id); // bump = 255
let (pda_254, bump_254) = Pubkey::create_program_address(&[seeds, &[254]], program_id); // Also valid!
```
**Issue:** Same seeds can derive multiple PDAs with different bumps. Creates confusion and potential exploits.
**Solution:** Always use canonical bump (255 counting down to first valid). Anchor's `bump` constraint enforces this.
### 3. Compute Budget Limits
| Network | Base Compute Units | With Optimization |
|---------|-------------------|-------------------|
| Mainnet | 200,000 | Up to 1,400,000 (with request) |
| Devnet | 200,000 | Up to 1,400,000 |
**Issue:** Complex programs can exceed compute budget, causing transaction failure.
**Optimization strategies:**
- Minimize CPIs (each costs ~1000 CU)
- Use `AccountLoader` for large accounts
- Avoid loops with variable length
- Request higher compute budget: `ComputeBudgetProgram::set_compute_unit_limit()`
### 4. Transaction Size Limit
**Hard limit:** ~1232 bytes for transaction
**Implications:**
- Limits number of accounts (~35-40 accounts typical max)
- Large instructions need Account Compression or chunking
- Can't pass large data directly in instruction
**Solutions:**
- Use PDAs to store large data
- Break operations into multiple transactions
- Use lookup tables for frequent accounts
### 5. Account Snapshot Loading
```rust
let balance_before = ctx.accounts.vault.balance;
// CPI happens here
// balance_before is STALE - account was loaded before CPI
```
**Issue:** Accounts are loaded as snapshots at transaction start. Modifications during transaction (via CPIs) don't update the loaded data.
**Solution:** Call `.reload()` after any CPI that might modify the account.
## Token Program Gotchas
### 1. ATA Addresses Are Deterministic But Not Guaranteed
```rust
let ata = get_associated_token_address(&owner, &mint);
// ata address is deterministic but account might not exist!
```
**Issue:** ATA address can be calculated but account may not be initialized.
**Solution:** Check account exists and is initialized before use, or use `init_if_needed` with proper validation.
### 2. Delegates Don't Automatically Reset
```rust
// After transfer of ownership:
token_account.owner = new_owner;
// BUT: delegate and delegated_amount are NOT reset!
```
**Issue:** Changing owner doesn't clear delegate/close authority. Old delegate can still spend.
**Solution:** Explicitly reset authorities when changing ownership:
```rust
account.delegate = COption::None;
account.delegated_amount = 0;
if account.is_native() {
account.close_authority = COption::None;
}
```
### 3. Token-2022 Extension Rent
**Issue:** Each extension adds rent cost. Account size varies by extensions enabled.
**Extensions and their sizes:**
- Transfer Fee: ~83 bytes
- Transfer Hook: ~107 bytes
- Permanent Delegate: ~36 bytes
- Interest Bearing: ~40 bytes
**Solution:** Calculate rent based on all enabled extensions.
### 4. Token-2022 Transfer Hooks Can Be Malicious
```rust
// Transfer hook can call arbitrary program!
pub struct TransferHookAccount {
pub program_id: Pubkey, // Could be malicious
}
```
**Issue:** Transfer hook extensions allow calling external program during transfers. Malicious hook can fail transaction or drain funds.
**Solution:**
- Validate transfer hook program if accepting specific tokens
- Consider disallowing tokens with transfer hooks
- Use Anchor's `TransferChecked` instruction
## Testing Blind Spots
### 1. Concurrent Transaction Ordering
**Issue:** Tests typically run transactions sequentially. In production, concurrent transactions can interleave in unexpected ways.
**Vulnerability example:**
```rust
// Transaction 1: Check balance = 100
// Transaction 2: Withdraw 80 (balance now 20)
// Transaction 1: Withdraw 80 (uses stale check, balance now -60!)
```
**Mitigation:**
- Use atomic operations
- Reload accounts before critical operations
- Design for idempotency
### 2. Account Rent Reclaim Attacks
**Issue:** When account rent falls below minimum, validator can reclaim the account. Tests don't simulate this.
**Solution:** Ensure all accounts are rent-exempt (2+ years of rent).
### 3. Sysvar Manipulation in Tests
```rust
// In tests, you can set arbitrary clock values
ctx.accounts.clock = Clock { unix_timestamp: attacker_value, ... };
```
**Issue:** Tests may not catch reliance on tamper-resistant sysvars.
**Solution:** In production, always load sysvars from official sysvar accounts:
```rust
pub clock: Sysvar<'info, Clock>, // Validated address
```
### 4. Devnet vs Mainnet Differences
| Aspect | Devnet | Mainnet |
|--------|--------|---------|
| Oracle prices | Often stale/fake | Real-time |
| Program versions | May differ | Stable versions |
| Compute limits | More lenient | Strict |
| Congestion | Minimal | Can be high |
| Token availability | Test tokens | Real value |
**Issue:** Programs tested only on devnet may fail on mainnet.
**Solution:** Test on mainnet-fork or mainnet with small amounts before full deployment.
## Rust-Specific Gotchas
### 1. `unwrap()` Panics
```rust
// Panics kill the entire transaction!
let value = some_option.unwrap(); // ❌ Never do this
```
**Solution:** Always use proper error handling:
```rust
let value = some_option.ok_or(ErrorCode::MissingValue)?;
```
### 2. Integer Division Truncation
```rust
let result = 5 / 2; // result = 2, not 2.5!
```
**Issue:** Integer division truncates, potentially causing precision loss in financial calculations.
**Solution:** Use `Decimal` type for precise calculations, or multiply before divide:
```rust
let result = (5 * PRECISION) / 2 / PRECISION;
```
### 3. Overflow in Debug vs Release
```rust
// Debug mode: panics on overflow
// Release mode: wraps silently!
let x: u8 = 255;
let y = x + 1; // Debug: panic, Release: y = 0
```
**Solution:** Always use `checked_*` methods - they work same in debug and release.
## Cross-Program Invocation (CPI) Gotchas
### 1. CPI Success Doesn't Guarantee Correct State
```rust
// CPI returns success but state may be unexpected
invoke(&transfer_instruction, &accounts)?;
// Transfer succeeded but amount might be different due to fees!
```
**Solution:** Reload and validate account state after CPI.
### 2. Signer Seeds Must Be Exact
```rust
// Seeds for signing must match PDA derivation exactly
let seeds = &[
b"vault",
user.key().as_ref(),
&[bump], // Must be same bump used to derive PDA
];
invoke_signed(&instruction, &accounts, &[seeds])?;
```
**Issue:** Wrong seeds = "signature verification failed" error.
### 3. CPI Depth Limit
**Limit:** 4 levels of CPI depth
**Issue:** Program A → Program B → Program C → Program D → Program E (fails!)
**Solution:** Design programs to minimize CPI depth.
## Common Misunderstandings
### 1. "Anchor Prevents All Security Issues"
**False:** Anchor prevents some common issues (missing discriminators, wrong account types) but doesn't validate business logic, arithmetic, or authorization.
### 2. "Devnet Testing Is Sufficient"
**False:** Mainnet has different compute limits, real oracle data, congestion, and MEV considerations.
### 3. "One Audit Makes Code Secure"
**False:** Audits find issues in a snapshot. Code changes after audit reintroduce risk. Need continuous security review.
### 4. "`checked_*` Methods Are Slower"
**False:** Rust compiler optimizes these similarly to unchecked arithmetic. Always use checked methods.
### 5. "PDAs Can't Sign"
**True for external transactions, false for CPIs:** PDAs can sign CPIs using `invoke_signed` but can't sign transactions directly.
## Version-Specific Issues
### Anchor Version Compatibility
- **< 0.28**: No `InitSpace` derive, manual space calculation error-prone
- **< 0.29**: Different constraint syntax
- **0.30+**: Breaking changes in error handling and account initialization
**Solution:** Check `Cargo.toml` for version and consult [Anchor Changelog](https://github.com/coral-xyz/anchor/blob/master/CHANGELOG.md).
### Solana Version Differences
- **Pre-1.14**: Different fee structure
- **Pre-1.16**: No Address Lookup Tables
- **Pre-1.17**: No Token-2022
**Solution:** Verify target Solana version matches deployment network.
---
**Key Takeaway:** Many "obvious" assumptions about blockchain behavior don't hold in Solana. Always validate against actual runtime behavior, not assumptions from other chains.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,177 @@
# Resources
Comprehensive collection of official documentation, security guides, audit reports, and learning materials for Solana development and security.
## Official Documentation
### Solana Core
- [Solana Docs](https://solana.com/docs/) - Official Solana documentation
- [Solana Cookbook](https://solana.com/developers/cookbook) - Recipes for common Solana tasks
- [Solana Courses](https://solana.com/developers/courses/) - Official learning paths
- [Program Examples](https://github.com/solana-developers/program-examples) - Multi-framework examples
- [Developer Bootcamp 2024](https://github.com/solana-developers/developer-bootcamp-2024)
### Anchor Framework
- [Anchor Docs](https://www.anchor-lang.com/docs) - Official Anchor documentation
- [Anchor Book](https://book.anchor-lang.com/) - Comprehensive Anchor guide
- [Anchor by Example](https://examples.anchor-lang.com/) - Example programs
- [Anchor Lang Docs](https://docs.rs/anchor-lang) - API documentation
- [Anchor SPL Docs](https://docs.rs/anchor-spl) - SPL integration helpers
### SPL Programs
- [SPL Documentation](https://spl.solana.com/) - Solana Program Library docs
- [Token Program](https://github.com/solana-program/token) - SPL Token source
- [Token-2022](https://github.com/solana-program/token-2022) - Next-gen token program
- [Associated Token Account](https://github.com/solana-program/associated-token-account)
- [Token Metadata](https://github.com/solana-program/token-metadata)
- [Metaplex Token Metadata](https://github.com/metaplex-foundation/mpl-token-metadata)
## Security Resources
### Curated Security Lists
- [Awesome Solana Security (0xMacro)](https://github.com/0xMacro/awesome-solana-security) - **Actively maintained**, comprehensive resource list
- [Rektoff Security Roadmap](https://github.com/Rektoff/Security-Roadmap-for-Solana-applications) - Full lifecycle security strategy
- [SlowMist Best Practices](https://github.com/slowmist/solana-smart-contract-security-best-practices) - Common pitfalls with examples
- [Ackee Solana Handbook](https://ackee.xyz/solana/book/latest/) - Comprehensive development guide
### Security Guides & Articles
- [Helius Security Guide](https://www.helius.dev/blog/a-hitchhikers-guide-to-solana-program-security) - Common vulnerabilities explained
- [Neodyme Breakpoint Workshop](https://github.com/neodyme-labs/neodyme-breakpoint-workshop) - Hands-on security training
- [Solana Security Course](https://solana.com/developers/courses/program-security) - Official security course
- [Asymmetric Research CPI Vulnerabilities](https://blog.asymmetric.re/invocation-security-navigating-vulnerabilities-in-solana-cpis/)
- [Ottersec Lamport Transfers](https://osec.io/blog/2025-05-14-king-of-the-sol) - SOL transfer vulnerabilities
- [Infect3d Auditing Essentials](https://www.infect3d.xyz/blog/solana-quick-start)
### Vulnerability Collections
- [Urataps Audit Examples](https://github.com/urataps/solana-audit-examples) - Programs with vulnerabilities
- [ImmuneBytes Attack Vectors](https://github.com/ImmuneBytes-Security-Audit/Blockchain-Attack-Vectors/tree/main/Solana%20Attack%20Vectors)
- [Exvul Security Guide](https://exvul.com/rust-smart-contract-security-guide-in-solana/)
- [Nirlin Advanced Vulnerabilities](https://substack.com/inbox/post/164534668)
### Video Tutorials
- [Zigtur Security Walkthrough](https://www.youtube.com/watch?v=xd6qfY-GDYY)
- [M4rio Security Walkthrough](https://www.youtube.com/watch?v=q4z8tIi43lg)
### Token-2022 Security
- [Offside Token-2022 Part 1](https://blog.offside.io/p/token-2022-security-best-practices-part-1)
- [Offside Token-2022 Part 2](https://blog.offside.io/p/token-2022-security-best-practices-part-2)
- [Neodyme Token-2022 Security](https://neodyme.io/en/blog/token-2022)
### Deep Dives & Research
- [r0bre's 100 Daily Solana Tips](https://accretionxyz.substack.com/p/r0bres-100-daily-solana-tips)
- [Accretion Hidden IDL Instructions](https://accretionxyz.substack.com/p/hidden-idl-instructions-and-how-to)
- [Farouk ELALEM Under the Hood](https://ubermensch.blog/under-the-hood-of-solana-program-execution-from-rust-code-to-sbf-bytecode)
- [Lucrative_Panda Security History](https://medium.com/@lucrativepanda/a-comprehensive-analysis-of-solanas-security-history-all-incidents-impacts-and-evolution-up-to-1b1564c7ddfe)
## Essential Codebases to Study
Study these production codebases to learn security patterns:
### Framework & Core Programs
- [Anchor Framework](https://github.com/solana-foundation/anchor) - The framework itself
- [Solana System Program](https://github.com/solana-program/system)
- [SPL Token Program](https://github.com/solana-program/token)
- [Token-2022](https://github.com/solana-program/token-2022)
### Production Protocols
- [Raydium AMM](https://github.com/raydium-io/raydium-cp-swap) - DEX protocol
- [Kamino Lending](https://github.com/Kamino-Finance/klend) - Lending protocol
- [Squads Multisig](https://github.com/Squads-Protocol/v4) - Multisig protocol
## Audit Reports
Study real security audits to learn from actual vulnerabilities:
### Code4rena
- [Pump Science](https://code4rena.com/reports/2025-01-pump-science) - 2 High, 3 Medium
### Sherlock
- [Orderly](https://audits.sherlock.xyz/contests/524/report) - 2 High, 1 Medium
- [WOOFi](https://audits.sherlock.xyz/contests/535/report) - 2 High, 3 Medium
### Cantina
Contact `0xmorph` in Cantina Discord for read access:
- [Grass](https://cantina.xyz/competitions/3211ee0d-133f-43a0-837e-8dc1ecfaa424) - 13 High, 6 Medium
- [Olas](https://cantina.xyz/competitions/829164bf-7fba-4b84-a6b8-76652205bd97) - 2 High, 3 Medium
- [Tensor](https://cantina.xyz/competitions/21787352-de2c-4a77-af09-cc0a250d1f04) - 5 High, 10 Medium
- [ZetaChain](https://cantina.xyz/competitions/80a33cf0-ad69-4163-a269-d27756aacb5e) - 6 High, 27 Medium
- [Inclusive Finance](https://cantina.xyz/competitions/3eff5a8f-b73a-4cfe-8c54-546b475548f0) - 45 High, 25 Medium
- [Reserve Index](https://cantina.xyz/code/8b94becd-54e7-41cd-88e6-caae7becc76a) - 10 High, 11 Medium
## Learning Paths
### For EVM Developers
- [RareSkills Solana Course](https://www.rareskills.io/solana-tutorial) - Ethereum to Solana
- [0xkowloon Anchor for EVM](https://0xkowloon.gitbook.io/anchor-for-evm-developers)
### For Rust Learners
- [Rust Book](https://doc.rust-lang.org/book/)
- [Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html)
### Native Rust (Non-Anchor)
- [Solana Native Rust Docs](https://solana.com/docs/programs/rust)
- [Native Development Course](https://solana.com/developers/courses/native-onchain-development)
### Blueshift Challenges
- [Blueshift Courses](https://learn.blueshift.gg/) - Anchor and Pinocchio
## Tools
### Development
- [Solana Playground](https://beta.solpg.io/) - Browser-based IDE
- [Rust Playground](https://play.rust-lang.org/) - Test Rust snippets
### Security & Analysis
- [Trident](https://github.com/Ackee-Blockchain/trident) - Fuzz testing framework
- [Certora Prover](https://docs.certora.com/en/latest/docs/solana/index.html) - Formal verification
- [Sec3 IDL Guesser](https://github.com/sec3-service/IDLGuesser) - Reverse engineer IDLs
- [Anchor X-ray](https://github.com/crytic/anchorx-ray) - Visualize accounts (Trail of Bits)
- [Anchor Version Detector](https://github.com/johnsaigle/anchor-version-detector) - Compatibility checker
### Testing
- [Anchor Test Framework](https://book.anchor-lang.com/anchor_in_depth/testing.html)
- [Solana Test Validator](https://docs.solana.com/developing/test-validator)
## CTFs & Practice
### Capture The Flag
- [Ackee Solana CTF](https://github.com/Ackee-Blockchain/Solana-Auditors-Bootcamp/tree/master/Capture-the-Flag)
### Bootcamps
- [Rektoff 6-Week Bootcamp](https://www.rektoff.xyz/bootcamp) - Free, Solana Foundation supported
- [Ackee Auditors Bootcamp](https://ackee.xyz/solana-auditors-bootcamp)
## Community & Support
### Q&A Platforms
- [Solana Stack Exchange](https://solana.stackexchange.com/)
### Blogs & Newsletters
- [Helius Blog](https://www.helius.dev/blog) - Frequent Solana content
- [Pine Analytics Substack](https://substack.com/@pineanalytics1) - Protocol deep dives
## Security Firms
Top firms for Solana security audits:
- [Runtime Verification](https://runtimeverification.com/)
- [OtterSec](https://osec.io/)
- [Neodyme](https://neodyme.io/en/)
- [Sec3](https://www.sec3.dev/)
- [Zellic](https://www.zellic.io/)
- [Ackee Blockchain](https://ackee.xyz/)
- [Hexens](https://hexens.io/)
- [Trail of Bits](https://www.trailofbits.com/)
- [Kudelski Security](https://kudelskisecurity.com/)
- [Cantina](https://cantina.xyz/)
- [Certora](https://www.certora.com/)
- [Sherlock](https://www.sherlock.xyz/)
## Version Information
- Latest Anchor version (as of 2025): 0.30+
- Recommended Solana CLI: Latest stable
- Rust minimum version: 1.70+
---
**Note:** This is a curated collection from the Awesome Solana Security repository and other trusted sources. Resources are selected for their quality, maintenance status, and relevance to modern Solana development practices.

View File

@@ -0,0 +1,291 @@
# Security Checklists
Comprehensive validation checklists for Solana program security reviews.
## Account Validation Checklist
For every account in every instruction:
- [ ] **Signer validation**: Uses `Signer<'info>` or `is_signer` check when needed
- [ ] **Owner validation**: Uses `#[account(owner = ...)]` or manual owner check
- [ ] **Writable checks**: Properly marked `mut` when account data will be modified
- [ ] **Account initialization**: Checks if account is initialized before use
- [ ] **PDA validation**: Validates seeds and uses canonical bump
- [ ] **Discriminator check**: For `AccountLoader`, validates account type
- [ ] **Account relationships**: Uses `has_one` for related accounts
```rust
// Complete account validation example
#[derive(Accounts)]
pub struct SecureInstruction<'info> {
#[account(
mut,
has_one = authority, // Relationship validation
seeds = [b"vault", authority.key().as_ref()],
bump, // Canonical bump
)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>, // Signer required
#[account(
mut,
constraint = token_account.owner == authority.key(), // Custom validation
)]
pub token_account: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>, // Program validation
}
```
## Arithmetic Safety Checklist
For all mathematical operations:
- [ ] **Addition**: Uses `checked_add()` instead of `+`
- [ ] **Subtraction**: Uses `checked_sub()` instead of `-`
- [ ] **Multiplication**: Uses `checked_mul()` instead of `*`
- [ ] **Division**: Uses `checked_div()` instead of `/`
- [ ] **Division by zero**: Validates divisor is non-zero
- [ ] **Precision loss**: Uses `try_floor_u64()` instead of `try_round_u64()` to prevent arbitrage
- [ ] **Avoid saturating**: Does not use `saturating_*` methods (they hide errors)
- [ ] **Proper error handling**: All arithmetic wrapped in `ok_or(error)?`
```rust
// Secure arithmetic examples
let total = balance
.checked_add(amount)
.ok_or(ErrorCode::Overflow)?;
let share = total
.checked_div(denominator)
.ok_or(ErrorCode::DivisionByZero)?;
// For Decimal types (token amounts)
let liquidity = Decimal::from(collateral_amount)
.try_div(rate)?
.try_floor_u64()?; // Not try_round_u64()!
```
## PDA and Account Security Checklist
- [ ] **Canonical bump**: PDAs use `bump` in seeds constraint (not hardcoded)
- [ ] **Unique seeds**: Seeds include unique identifier (user pubkey, mint, etc.)
- [ ] **No duplicate accounts**: Same account not used twice as mutable
- [ ] **Init vs init_if_needed**: Uses `init` with proper validation, not `init_if_needed`
- [ ] **has_one constraints**: Related accounts validated with `has_one`
- [ ] **Custom constraints**: Complex validation uses `constraint` expression
- [ ] **Seed collision**: Seeds designed to prevent collisions
```rust
// Secure PDA patterns
#[account(
init,
payer = authority,
space = 8 + UserAccount::INIT_SPACE,
seeds = [
b"user",
authority.key().as_ref(), // Unique to user
mint.key().as_ref(), // Unique to mint
],
bump
)]
pub user_account: Account<'info, UserAccount>,
```
## CPI Security Checklist
For all Cross-Program Invocations:
- [ ] **Program validation**: Target program is validated (uses `Program<'info, T>`)
- [ ] **Signer seeds**: PDA signers pass seeds correctly in `invoke_signed`
- [ ] **Return value checking**: CPI success doesn't guarantee correct state
- [ ] **Account reloading**: Reload accounts after CPI that may modify them
- [ ] **No arbitrary CPI**: Program account is not user-controlled
- [ ] **Privilege escalation**: CPI doesn't grant unexpected permissions
```rust
// Secure CPI pattern
#[derive(Accounts)]
pub struct SecureCPI<'info> {
pub token_program: Program<'info, Token>, // Type-validated
// ... other accounts
}
pub fn secure_cpi(ctx: Context<SecureCPI>) -> Result<()> {
// CPI with validated program
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;
// Reload account after CPI
ctx.accounts.from.reload()?;
// Validate expected state
require!(
ctx.accounts.from.amount == expected_amount,
ErrorCode::InvalidState
);
Ok(())
}
```
## Oracle and External Data Checklist
For Pyth, Switchboard, or other oracles:
- [ ] **Oracle status**: Validates oracle is in valid state (Trading status for Pyth)
- [ ] **Price staleness**: Checks timestamp is recent enough
- [ ] **Oracle owner**: Validates oracle account owner is correct program
- [ ] **Confidence interval**: For Pyth, checks confidence is acceptable
- [ ] **Price validity**: Validates price is within reasonable bounds
- [ ] **Fallback handling**: Has strategy for oracle failure
```rust
// Pyth oracle validation
pub fn validate_pyth_price(
pyth_account: &AccountInfo,
clock: &Clock,
) -> Result<i64> {
// Validate owner
require_keys_eq!(
*pyth_account.owner,
PYTH_PROGRAM_ID,
ErrorCode::InvalidOracle
);
let price_data = pyth_account.try_borrow_data()?;
let price_feed = load_price_feed_from_account_info(pyth_account)?;
// Check status
require!(
price_feed.agg.status == PriceStatus::Trading,
ErrorCode::InvalidOracleStatus
);
// Check staleness (e.g., max 60 seconds old)
let max_age = 60;
require!(
clock.unix_timestamp - price_feed.agg.publish_time <= max_age,
ErrorCode::StalePrice
);
// Check confidence (example: max 1% of price)
let confidence_threshold = price_feed.agg.price / 100;
require!(
price_feed.agg.conf <= confidence_threshold as u64,
ErrorCode::OracleConfidenceTooLow
);
Ok(price_feed.agg.price)
}
```
## Token Program Security Checklist
### SPL Token Checks
- [ ] **ATA validation**: Associated Token Accounts validated correctly
- [ ] **Mint authority**: Proper checks on mint authority for minting operations
- [ ] **Freeze authority**: Handles frozen accounts appropriately
- [ ] **Delegate handling**: Resets delegate when needed
- [ ] **Close authority**: Resets close authority on owner change
### Token-2022 Specific Checks
- [ ] **Transfer hooks**: Handles transfer hook extensions correctly
- [ ] **Extension data**: Validates all active extensions
- [ ] **Confidential transfers**: Properly handles confidential transfer extension
- [ ] **Transfer fees**: Respects transfer fee extension
- [ ] **Permanent delegate**: Checks for permanent delegate extension
- [ ] **Additional rent**: Accounts for extension rent requirements
```rust
// Token-2022 with extensions
use spl_token_2022::extension::{
BaseStateWithExtensions,
StateWithExtensions,
};
pub fn safe_token_2022_transfer(
/* accounts */
) -> Result<()> {
// Check for transfer hook
let mint_data = mint.try_borrow_data()?;
let mint_with_extensions = StateWithExtensions::<Mint>::unpack(&mint_data)?;
if let Ok(transfer_hook) = mint_with_extensions.get_extension::<TransferHook>() {
// Handle transfer hook properly
// ... transfer hook logic
}
// Check for transfer fee
if let Ok(transfer_fee_config) = mint_with_extensions.get_extension::<TransferFeeConfig>() {
// Calculate and handle fees
// ... fee logic
}
// Proceed with transfer
Ok(())
}
```
## Architecture Review Checklist
- [ ] **PDA design**: PDAs used appropriately vs keypair accounts
- [ ] **Account space**: Space calculation uses `InitSpace` derive
- [ ] **Error handling**: Custom errors with descriptive messages
- [ ] **Event emission**: Critical state changes emit events
- [ ] **Rent exemption**: All accounts are rent-exempt
- [ ] **Transaction size**: Stays within ~1232 byte limit
- [ ] **Compute budget**: Optimized to stay under compute limits
- [ ] **Upgradeability**: Considers upgrade path and account versioning
## Testing Checklist
- [ ] **Unit tests**: Each instruction has unit tests
- [ ] **Fuzz tests**: Arithmetic operations have fuzz tests (Trident)
- [ ] **Integration tests**: Realistic multi-instruction scenarios
- [ ] **Negative tests**: Tests for expected failures
- [ ] **PDA tests**: Tests for seed collisions
- [ ] **Edge cases**: Zero amounts, max values, overflow boundaries
- [ ] **Concurrency**: Tests for transaction ordering issues
- [ ] **Devnet testing**: Deployed and tested on devnet
```rust
// Example test structure
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normal_case() {
// Test expected behavior
}
#[test]
#[should_panic(expected = "Overflow")]
fn test_overflow() {
// Test arithmetic overflow protection
}
#[test]
fn test_unauthorized_access() {
// Test fails with wrong signer
}
#[test]
fn test_edge_case_zero_amount() {
// Test zero amount handling
}
}
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,525 @@
# Common Vulnerability Patterns
Detailed examples of common Solana smart contract vulnerabilities with exploit scenarios and secure alternatives.
## 1. Missing Signer Validation
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
// No check that caller is authorized!
let vault = &mut ctx.accounts.vault;
vault.balance -= amount;
Ok(())
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
pub user: AccountInfo<'info>, // Not a Signer!
}
```
### Exploit Scenario
Attacker can drain the vault by calling `withdraw` with any account as the `user` parameter. No signature verification means anyone can execute the instruction.
### Secure Alternative
```rust
// ✅ SECURE
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
mut,
has_one = authority, // Ensures vault.authority == authority.key()
)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>, // Must sign transaction
}
```
## 2. Integer Overflow/Underflow
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
let vault = &mut ctx.accounts.vault;
vault.balance = vault.balance + amount; // Can overflow!
Ok(())
}
```
### Exploit Scenario
If `vault.balance = u64::MAX - 100` and `amount = 200`, the addition overflows and wraps to `99`, effectively stealing funds from the vault.
### Secure Alternative
```rust
// ✅ SECURE
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
let vault = &mut ctx.accounts.vault;
vault.balance = vault
.balance
.checked_add(amount)
.ok_or(ErrorCode::Overflow)?;
Ok(())
}
```
## 3. PDA Substitution Attack
### Vulnerability
```rust
// ❌ VULNERABLE
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub config: Account<'info, Config>, // No PDA validation!
#[account(
mut,
seeds = [b"vault", config.key().as_ref()], // Uses unvalidated config
bump
)]
pub vault: Account<'info, Vault>,
}
```
### Exploit Scenario
Attacker creates a fake `config` account with malicious settings. The `vault` PDA is derived from this fake config, potentially accessing wrong vault or bypassing security checks.
### Secure Alternative
```rust
// ✅ SECURE
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(
seeds = [b"config"], // Global config PDA
bump,
)]
pub config: Account<'info, Config>,
#[account(
mut,
seeds = [b"vault", config.key().as_ref()],
bump
)]
pub vault: Account<'info, Vault>,
}
```
## 4. Type Cosplay Attack
### Vulnerability
```rust
// ❌ VULNERABLE
#[account(mut)]
pub user: AccountLoader<'info, User>, // Doesn't check discriminator!
```
### Exploit Scenario
Attacker passes a `UserAdmin` account instead of `User`. Since `AccountLoader` doesn't check discriminators by default, the program treats the admin account as a regular user, potentially bypassing privilege checks.
### Secure Alternative
```rust
// ✅ SECURE
#[account(mut)]
pub user: Account<'info, User>, // Enforces correct discriminator
```
## 5. Account Reloading Issues
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn complex_operation(ctx: Context<ComplexOp>) -> Result<()> {
let initial_balance = ctx.accounts.vault.balance;
// CPI that modifies vault
transfer_tokens(&ctx)?;
// Still using stale balance!
require!(
ctx.accounts.vault.balance >= initial_balance,
ErrorCode::InvalidBalance
);
Ok(())
}
```
### Exploit Scenario
The `balance` value is cached from before the CPI. If the CPI modified the vault, the check uses stale data, potentially allowing invalid state transitions.
### Secure Alternative
```rust
// ✅ SECURE
pub fn complex_operation(ctx: Context<ComplexOp>) -> Result<()> {
transfer_tokens(&ctx)?;
// Reload account to get fresh data
ctx.accounts.vault.reload()?;
require!(
ctx.accounts.vault.balance >= expected_balance,
ErrorCode::InvalidBalance
);
Ok(())
}
```
## 6. Improper Account Closing
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
**ctx.accounts.vault.to_account_info().lamports.borrow_mut() = 0;
// Data not zeroed, authority not reset!
Ok(())
}
```
### Exploit Scenario
Account data remains accessible within the same transaction even after lamports are zeroed. Attacker can read sensitive data or reuse the account in unexpected ways.
### Secure Alternative
```rust
// ✅ SECURE
#[derive(Accounts)]
pub struct CloseAccount<'info> {
#[account(
mut,
close = receiver // Properly closes: transfers lamports, zeros data
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub receiver: SystemAccount<'info>,
}
```
## 7. Missing Lamports Check
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn process(ctx: Context<Process>) -> Result<()> {
let data = ctx.accounts.user_data.load()?; // Can read closed account!
// ... use data
Ok(())
}
```
### Exploit Scenario
Account was closed earlier in the transaction but data is still readable. Processing closed account data can lead to inconsistent state or bypass business logic.
### Secure Alternative
```rust
// ✅ SECURE
pub fn process(ctx: Context<Process>) -> Result<()> {
require!(
**ctx.accounts.user_data.to_account_info().lamports.borrow() > 0,
ErrorCode::AccountClosed
);
let data = ctx.accounts.user_data.load()?;
// ... use data
Ok(())
}
```
## 8. Arbitrary CPI
### Vulnerability
```rust
// ❌ VULNERABLE
#[derive(Accounts)]
pub struct ArbitraryCPI<'info> {
pub token_program: AccountInfo<'info>, // Not validated!
}
pub fn transfer(ctx: Context<ArbitraryCPI>) -> Result<()> {
invoke(
&transfer_instruction,
&[
ctx.accounts.token_program.clone(), // Could be malicious!
// ...
]
)?;
Ok(())
}
```
### Exploit Scenario
Attacker passes malicious program instead of real Token program. Malicious program can emit fake events, return success without transferring, or drain funds.
### Secure Alternative
```rust
// ✅ SECURE
#[derive(Accounts)]
pub struct SecureCPI<'info> {
pub token_program: Program<'info, Token>, // Type-checked!
}
// Or manual validation
require_keys_eq!(
*ctx.accounts.token_program.key,
spl_token::ID,
ErrorCode::InvalidTokenProgram
);
```
## 9. Duplicate Mutable Accounts
### Vulnerability
```rust
// ❌ VULNERABLE
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
}
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
ctx.accounts.from.amount -= amount;
ctx.accounts.to.amount += amount; // Same account = double amount!
Ok(())
}
```
### Exploit Scenario
If `from` and `to` are the same account, the user can double their balance by transferring to themselves.
### Secure Alternative
```rust
// ✅ SECURE
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(
mut,
constraint = from.key() != to.key() @ ErrorCode::SameAccount
)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
}
```
## 10. Bump Seed Canonicalization
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn init_vault(ctx: Context<InitVault>, bump: u8) -> Result<()> {
// Accepts any bump from user!
let seeds = &[b"vault", user.key().as_ref(), &[bump]];
// Multiple PDAs possible for same seeds!
}
```
### Exploit Scenario
Attacker can create multiple vault PDAs with different bumps for the same user, fragmenting state or confusing off-chain systems.
### Secure Alternative
```rust
// ✅ SECURE
#[derive(Accounts)]
pub struct InitVault<'info> {
#[account(
init,
payer = user,
space = 8 + Vault::INIT_SPACE,
seeds = [b"vault", user.key().as_ref()],
bump // Anchor derives and stores canonical bump automatically
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
```
## 11. Missing Owner Check
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn read_data(ctx: Context<ReadData>) -> Result<()> {
let oracle_data = ctx.accounts.oracle.try_borrow_data()?;
// No check that oracle is owned by Pyth program!
let price = parse_price(&oracle_data)?;
Ok(())
}
```
### Exploit Scenario
Attacker creates fake oracle account owned by their own program, filled with manipulated price data. Program trusts the fake data.
### Secure Alternative
```rust
// ✅ SECURE
pub fn read_data(ctx: Context<ReadData>) -> Result<()> {
require_keys_eq!(
*ctx.accounts.oracle.owner,
PYTH_PROGRAM_ID,
ErrorCode::InvalidOracleOwner
);
let oracle_data = ctx.accounts.oracle.try_borrow_data()?;
let price = parse_price(&oracle_data)?;
Ok(())
}
```
## 12. Precision Loss / Rounding Errors
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn calculate_shares(collateral: u64, rate: Decimal) -> Result<u64> {
Decimal::from(collateral)
.try_div(rate)?
.try_round_u64() // Rounding can be exploited!
}
```
### Exploit Scenario
Attacker repeatedly deposits/withdraws small amounts. Rounding up gives slightly more shares each time, slowly draining the pool.
### Secure Alternative
```rust
// ✅ SECURE
pub fn calculate_shares(collateral: u64, rate: Decimal) -> Result<u64> {
Decimal::from(collateral)
.try_div(rate)?
.try_floor_u64() // Always round down in user's favor
}
```
## 13. Unchecked Error Returns
### Vulnerability
```rust
// ❌ VULNERABLE
spl_token::instruction::transfer(
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
); // Return value ignored!
```
### Exploit Scenario
Transfer instruction fails silently but program continues as if it succeeded. State becomes inconsistent with actual token balances.
### Secure Alternative
```rust
// ✅ SECURE
invoke(
&spl_token::instruction::transfer(
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
)?, // Propagates error
&[
source.clone(),
destination.clone(),
authority.clone(),
],
)?;
// Or use Anchor's CPI helpers
token::transfer(ctx, amount)?;
```
## 14. Init If Needed Vulnerability
### Vulnerability
```rust
// ❌ VULNERABLE
#[account(
init_if_needed,
payer = user,
space = 8 + Account::INIT_SPACE
)]
pub user_account: Account<'info, UserAccount>,
```
### Exploit Scenario
If account already exists, initialization is skipped but existing data might be inconsistent or malicious. Can bypass initialization checks.
### Secure Alternative
```rust
// ✅ SECURE - Explicit initialization
#[account(
init,
payer = user,
space = 8 + Account::INIT_SPACE
)]
pub user_account: Account<'info, UserAccount>,
// Or if init_if_needed is truly needed, add validation
pub fn init_or_validate(ctx: Context<InitAccount>) -> Result<()> {
if ctx.accounts.user_account.is_initialized {
// Validate existing data
require!(
ctx.accounts.user_account.owner == ctx.accounts.user.key(),
ErrorCode::InvalidOwner
);
} else {
// Initialize new account
ctx.accounts.user_account.is_initialized = true;
ctx.accounts.user_account.owner = ctx.accounts.user.key();
}
Ok(())
}
```
## 15. Stale Oracle Data
### Vulnerability
```rust
// ❌ VULNERABLE
pub fn get_price(pyth_account: &AccountInfo) -> Result<i64> {
let price_feed = load_price_feed(pyth_account)?;
Ok(price_feed.agg.price) // No staleness check!
}
```
### Exploit Scenario
Oracle stopped updating hours ago due to network issues. Attacker exploits stale price to buy/sell at favorable outdated rates.
### Secure Alternative
```rust
// ✅ SECURE
pub fn get_price(
pyth_account: &AccountInfo,
clock: &Clock
) -> Result<i64> {
let price_feed = load_price_feed(pyth_account)?;
// Check publishing time
let max_age_seconds = 60;
require!(
clock.unix_timestamp - price_feed.agg.publish_time <= max_age_seconds,
ErrorCode::StaleOraclePrice
);
// Check status
require!(
price_feed.agg.status == PriceStatus::Trading,
ErrorCode::OracleNotTrading
);
Ok(price_feed.agg.price)
}
```