Skip to content

ArjunaSec/Solana-checklist

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 

Repository files navigation

Solana Smart Contract Security Checklist

Mathematics issue

Account Validation

Signer Validation

  • Signer not validated
// ❌ Bad: No signer validation
pub fn handler(ctx: Context<Handler>) -> Result<()> {
    // Missing signer check
    Ok(())
}

// ✅ Good: Validate signer
if !payer_account.is_signer {
    return Err(ProgramError::MissingRequiredSignature);
}

Writer Validation

  • Writer not validated
// ❌ Bad: No writable check
let state_account = next_account_info(accounts_iter)?;

// ✅ Good: Check if account is writable
let state_account = next_account_info(accounts_iter)?;
if !state_account.is_writable {
    return Err(ProgramError::InvalidAccountData);
}

Owner Validation

  • Owner not checked
// ❌ Bad: Using AccountInfo without owner check
pub mint: AccountInfo<'info>

// ✅ Good: Use typed account with ownership check
pub mint: Account<'info, Mint>

// ✅ Good: Explicit owner check
#[account(constraint = authority.owner == token_program::ID)]
  • Account initialized check missing
// ❌ Bad: No initialization check
#[account]
pub struct StateAccount {
    data: u64,
}

// ✅ Good: Check initialization
#[account(init, payer = user, space = 8 + 8)]
pub struct StateAccount {
    data: u64,
}
  • Missing check for the permission of caller
// ❌ Bad: No authority check
fn init_market(accounts: &[AccountInfo]) -> ProgramResult {
    Ok(())
}

// ✅ Good: Validate authority
fn init_market(accounts: &[AccountInfo]) -> ProgramResult {
    let authority = next_account_info(accounts_iter)?;
    if *authority.key != AUTHORIZED_KEY {
        return Err(ProgramError::InvalidAuthority);
    }
    Ok(())
}
  • Missing system account check
// ❌ Bad: No system account validation
let token_program_id = next_account_info(account_info_iter)?;

// ✅ Good: Validate system account
if *token_program_id.key != spl_token::ID {
    return Err(ProgramError::IncorrectProgramId);
}
  • Missing check for lamports
// ❌ Bad: Reading account data without lamports check
let data = account.try_borrow_data()?;

// ✅ Good: Check lamports before reading
if account.try_borrow_lamports()? > 0 {
    let data = account.try_borrow_data()?;
}

Account Management

  • Improper closing of the account
// ❌ Bad: No proper account closing
#[account(mut)]
pub account_to_close: Account<'info, MyAccount>

// ✅ Good: Proper account closing with rent collection
#[account(mut, close = recipient)]
pub account_to_close: Account<'info, MyAccount>
  • Reinitialization of the same account using init_if_needed
// ❌ Bad: Unsafe reinitialization
#[account(init_if_needed, ...)]

// ✅ Fixed version - remove init_if_needed
#[account(
    init,  // Requires explicit initialization
    payer = payer,
    space = 8 + 32,
    constraint = !account.is_initialized
)]

System Validation

  • Sysvar not validated
  • Remaining accounts not validated
  • Bump seed canonicalization missing

Arithmetic and Data Handling

  • Integer overflow/underflow not checked
// ❌ Bad: Unchecked arithmetic
let balance = account.balance + amount;

// ✅ Good: Checked arithmetic
let balance = account.balance.checked_add(amount)
    .ok_or(ProgramError::Overflow)?;
  • Loss of precision in calculations
  • Division by zero not handled
// ❌ Bad: Unchecked division
let result = total / divisor;

// ✅ Good: Check for zero before division
if divisor == 0 {
    return Err(ProgramError::InvalidArgument);
}
let result = total / divisor;
  • Error handling missing (unchecked Result types)

Program Interaction

  • Arbitrary CPI (Cross-Program Invocation) risks
  • Type cosplay vulnerabilities
  • Account data matching not verified

State Management

  • Timely state reset missing
  • Account data consistency not maintained

Oracle Integration

  • Pyth oracle validation missing

Best Practices

  • Use checked_add, checked_sub, checked_mul, checked_div for arithmetic operations

  • Hardcode and validate system program addresses

  • Implement proper error handling with ? operator

  • Validate all account permissions before operations

  • Use typed accounts instead of AccountInfo

// ❌ Bad: Using raw AccountInfo
pub struct Instructions<'info> {
    pub mint: AccountInfo<'info>
}

// ✅ Good: Using typed Account
pub struct Instructions<'info> {
    pub mint: Account<'info, Mint>
}
  • Validate PDA derivation
// ✅ Good: Validate PDA derivation
let (pda, bump) = Pubkey::find_program_address(
    &[b"seed", authority.key.as_ref()],
    program_id
);
if pda != *pda_account.key {
    return Err(ProgramError::InvalidSeeds);
}
  • Implement proper error types
// ✅ Good: Custom error types
#[error_code]
pub enum MyError {
    #[msg("Invalid authority provided")]
    InvalidAuthority,
    #[msg("Account not initialized")]
    NotInitialized,
}

Sysvar Validation

// ❌ Bad: No sysvar validation
let clock = Clock::from_account_info(&sysvar_clock)?;

// ✅ Good: Validate sysvar address
#[account(address = sysvar::clock::ID)]
pub sysvar_clock: AccountInfo<'info>

PDA Management

  • PDA sharing protection
// ✅ Good: PDA ownership check
#[account(
    init,
    payer = payer,
    space = 8 + 32,
    seeds = [b"vault", owner.key.as_ref()],
    bump
)]
pub vault: Account<'info, Vault>,
#[account(constraint = authority.key == vault.owner)]
pub authority: Signer<'info>

Rent Validation

// ❌ Old version
#[account(mut, constraint = account.get_lamports() >= Rent::get()?.minimum_balance(account.data_len()))]

// ✅ Fixed version
#[account(mut, constraint = account.to_account_info().lamports() >= Rent::get()?.minimum_balance(account.data_len()))]
pub account: Account<'info, MyData>

Token Program Validation

// ❌ Bad: No token program validation
pub token_program: AccountInfo<'info>

// ✅ Good: Validate token program
#[account(constraint = token_program.key == &spl_token::ID)]
pub token_program: Program<'info, Token>

Seed Collision Prevention

// ✅ Good: Unique PDA seeds
let (pda, bump) = Pubkey::find_program_address(
    &[b"unique_prefix", owner.key.as_ref(), token_mint.key.as_ref()],
    program_id
);

Safe Lamport Transfers

// ✅ Good: Safe PDA lamport transfer
invoke_signed(
    &system_instruction::transfer(
        pda_account.key,
        recipient.key,
        lamports,
    ),
    &[pda_account.clone(), recipient.clone(), system_program.clone()],
    &[&[b"vault", owner.key.as_ref(), &[bump]]]
)?;

Arbitrary CPI Protection

// ❌ Bad: Unchecked CPI
let cpi_ctx = CpiContext::new(
    arbitrary_program.clone(),
    accounts
);

// ✅ Good: Validate CPI program
#[account(constraint = cpi_program.key == &VALID_CPI_PROGRAM_ID)]
pub cpi_program: AccountInfo<'info>

Account Existence Check

// ❌ Bad: Lamports check
if account.lamports() > 0 { /*...*/ }

// ✅ Good: Data length check
if account.data_len() > 0 { /*...*/ }

Reinitialization Protection

// ✅ Good: Reinit protection
#[account(
    init,  // Requires explicit initialization
    payer = payer,
    space = 8 + 32,
    constraint = !account.is_initialized
)]
pub account: Account<'info, MyData>,
#[account(constraint = signer.key == account.owner)]
pub signer: Signer<'info>

Type Cosplay Protection

// ✅ Improved version with Anchor's automatic discriminator check
#[account]
pub account: Account<'info, MyAccount>  // Anchor automatically checks discriminator

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published