Solana Smart Contracts: Common Pitfalls and How to Avoid Them

·

Introduction

Greetings, developers!

We’re Neodyme, a security research team specializing in Solana blockchain audits. Over the past year, we’ve uncovered critical vulnerabilities in Solana’s core code and smart contracts, safeguarding roughly $1 billion in assets. While manual audits remain essential, we’re sharing our top findings to help you write more secure contracts.

Below, we detail the five most frequent vulnerabilities in Solana programs, complete with simplified examples and actionable fixes.


1. Missing Ownership Check

TL;DR

🔹 Always verify the AccountInfo::owner field for non-user-controlled accounts.
🔹 Use helper functions to validate ownership and return trusted types.

Description

Solana accounts include an owner field designating which program can modify their data. Failing to validate ownership lets attackers supply malicious accounts.

Example

fn withdraw_token_restricted(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let config = ConfigAccount::unpack(next_account_info(&mut accounts.iter())?)?;
    // Missing check: if config.owner != program_id  
    Ok(())
}

Fix: Add if config.owner != program_id to ensure the account is program-owned.


2. Missing Signer Check

TL;DR

🔹 Restrict sensitive instructions by verifying AccountInfo::is_signer.

Description

Without signer checks, attackers can spoof admin or user accounts.

Example

fn update_admin(accounts: &[AccountInfo]) -> ProgramResult {
    let admin = next_account_info(&mut accounts.iter())?;
    // Missing check: if !admin.is_signer  
    Ok(())
}

Fix: Add if !admin.is_signer to confirm transaction signing.


3. Integer Overflow/Underflow

TL;DR

🔹 Use checked_add and TryFrom to avoid wrapping arithmetic.

Description

Rust’s release mode (including BPF) silently wraps overflows.

Example

let amount: u32 = u32::MAX - 1000;
let total = amount + 1000; // Wraps to 899!

Fix: Replace + with amount.checked_add(FEE)?.ok_or(ProgramError::InvalidArgument)?.


4. Arbitrary Program Invocation

TL;DR

🔹 Always validate program IDs before invoke_signed.

Description

Users can supply malicious program clones.

Example

invoke_signed(&malicious_transfer_instruction, accounts, seeds)?;

Fix: Add if token_program.key != &spl_token::id().


5. Account Type Confusion

TL;DR

🔹 Add TYPE fields to structs and validate during deserialization.

Description

Raw account data lacks type safety.

Example

pub struct User {
    pub TYPE: u8,  // Set to 1
    pub balance: u64,
}

pub struct Config {
    pub TYPE: u8,  // Set to 2
    pub admin: Pubkey,
}

Fix: Reject accounts where TYPE mismatches.


FAQs

Q1: How do I securely validate account ownership?

A1: Use a helper function that checks owner and returns a trusted wrapper type.

Q2: What’s the risk of missing signer checks?

A2: Attackers can bypass admin restrictions by spoofing pubkeys.

Q3: Why avoid as for integer casts?

A3: It silently truncates. Use TryFrom for safe conversions.

👉 Explore Solana’s official documentation for deeper insights.


Conclusion

These vulnerabilities are just the tip of the iceberg. Always audit thoroughly and stay updated with Solana’s security advisories. For more advanced pitfalls, watch this space!

👉 Learn about Solana’s SPL token standards to enhance your contract security.