Files
gh-tenequm-claude-plugins-s…/skills/solana-security/references/anchor-security.md
2025-11-30 09:01:25 +08:00

1151 lines
27 KiB
Markdown

# Anchor Security Reference
This document covers security patterns, vulnerabilities, and best practices specific to the Anchor framework for Solana program development.
## 1. Anchor Constraint Security
### 1.1 Account Constraint Basics
Anchor's `#[account(...)]` constraints provide declarative validation of accounts passed to instructions. Proper use is critical for security.
**Core constraint types:**
- `init` - Initialize a new account
- `mut` - Mark account as mutable
- `has_one` - Verify relationship between accounts
- `seeds` and `bump` - Validate PDA derivation
- `constraint` - Custom validation expressions
- `close` - Close account and return rent
- `realloc` - Resize account data
### 1.2 init vs init_if_needed
**VULNERABLE - Using init_if_needed:**
```rust
#[derive(Accounts)]
pub struct UpdateConfig<'info> {
#[account(
init_if_needed,
payer = authority,
space = 8 + Config::INIT_SPACE,
seeds = [b"config"],
bump
)]
pub config: Account<'info, Config>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
```
**Issue:** `init_if_needed` allows re-initialization attacks. An attacker can close the account in a previous transaction, then re-initialize it with malicious data.
**SECURE - Separate init and update instructions:**
```rust
#[derive(Accounts)]
pub struct InitConfig<'info> {
#[account(
init,
payer = authority,
space = 8 + Config::INIT_SPACE,
seeds = [b"config"],
bump
)]
pub config: Account<'info, Config>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct UpdateConfig<'info> {
#[account(
mut,
seeds = [b"config"],
bump = config.bump
)]
pub config: Account<'info, Config>,
pub authority: Signer<'info>,
}
```
**When init_if_needed is acceptable:**
- Idempotent operations where re-initialization is safe
- Accounts with no state that matters (pure PDAs used only for signing)
- Always combine with additional constraints to prevent misuse
### 1.3 has_one Constraints for Relationships
**VULNERABLE - Missing has_one check:**
```rust
#[derive(Accounts)]
pub struct WithdrawFunds<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub owner: Signer<'info>,
#[account(mut)]
pub destination: SystemAccount<'info>,
}
pub fn withdraw_funds(ctx: Context<WithdrawFunds>, amount: u64) -> Result<()> {
// Missing validation: anyone can withdraw from any vault!
transfer_lamports(&ctx.accounts.vault, &ctx.accounts.destination, amount)?;
Ok(())
}
```
**SECURE - Using has_one:**
```rust
#[account]
pub struct Vault {
pub owner: Pubkey,
pub bump: u8,
}
#[derive(Accounts)]
pub struct WithdrawFunds<'info> {
#[account(
mut,
has_one = owner, // Validates vault.owner == owner.key()
seeds = [b"vault", owner.key().as_ref()],
bump = vault.bump
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub owner: Signer<'info>,
#[account(mut)]
pub destination: SystemAccount<'info>,
}
```
### 1.4 seeds and bump for PDA Validation
**VULNERABLE - Not validating PDA derivation:**
```rust
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
pub depositor: Signer<'info>,
}
```
**Issue:** Attacker can pass any account as vault, including one they control.
**SECURE - Validate PDA with seeds and bump:**
```rust
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(
mut,
seeds = [b"vault", depositor.key().as_ref()],
bump = vault.bump
)]
pub vault: Account<'info, Vault>,
pub depositor: Signer<'info>,
}
```
**CRITICAL: Always use canonical bump:**
```rust
#[account]
pub struct Vault {
pub bump: u8, // Store canonical bump at initialization
}
// At initialization, use:
#[account(
init,
payer = payer,
space = 8 + Vault::INIT_SPACE,
seeds = [b"vault", authority.key().as_ref()],
bump // Anchor automatically finds canonical bump
)]
pub vault: Account<'info, Vault>,
// Then store it:
vault.bump = ctx.bumps.vault; // ctx.bumps available in Anchor 0.29+
```
### 1.5 constraint Expressions and Pitfalls
**VULNERABLE - Using constraint without proper checks:**
```rust
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(
mut,
constraint = from.amount >= amount @ ErrorCode::InsufficientFunds
)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
}
```
**Issue:** Missing check that authority actually owns the from account!
**SECURE - Combine constraints appropriately:**
```rust
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(
mut,
has_one = authority, // Verify ownership
constraint = from.amount >= amount @ ErrorCode::InsufficientFunds
)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
}
```
**Constraint expression tips:**
- Use `@` to specify custom error codes
- Constraints execute after account deserialization
- Complex logic should go in instruction handler, not constraints
- Prefer built-in constraints (`has_one`, `seeds`) over custom `constraint`
### 1.6 close Constraint Security
**VULNERABLE - close without proper authorization:**
```rust
#[derive(Accounts)]
pub struct CloseAccount<'info> {
#[account(
mut,
close = destination
)]
pub account_to_close: Account<'info, MyAccount>,
#[account(mut)]
pub destination: SystemAccount<'info>,
}
```
**Issue:** Anyone can close the account and steal the rent!
**SECURE - Verify authorization before closing:**
```rust
#[derive(Accounts)]
pub struct CloseAccount<'info> {
#[account(
mut,
has_one = authority,
close = authority // Return rent to authorized party
)]
pub account_to_close: Account<'info, MyAccount>,
#[account(mut)]
pub authority: Signer<'info>,
}
```
**CRITICAL: close order matters:**
```rust
// WRONG - closes account before using it
#[account(
close = authority,
has_one = authority
)]
pub my_account: Account<'info, MyAccount>,
// CORRECT - validates before closing
#[account(
has_one = authority,
close = authority
)]
pub my_account: Account<'info, MyAccount>,
```
### 1.7 realloc Security Considerations
**VULNERABLE - realloc without validation:**
```rust
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(
mut,
realloc = 8 + 4 + new_data.len(),
realloc::payer = payer,
realloc::zero = false
)]
pub data_account: Account<'info, DataAccount>,
#[account(mut)]
pub payer: Signer<'info>,
}
```
**Issues:**
- No max size check (DoS via huge allocations)
- No authority check (anyone can realloc)
- `zero = false` might leak old data
**SECURE - Proper realloc constraints:**
```rust
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(
mut,
has_one = authority,
realloc = 8 + 4 + new_data.len(),
realloc::payer = authority,
realloc::zero = true, // Zero out old data
constraint = new_data.len() <= MAX_DATA_SIZE @ ErrorCode::DataTooLarge
)]
pub data_account: Account<'info, DataAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
```
## 2. Common Anchor Vulnerabilities
### 2.1 Missing Constraints Leading to Account Substitution
**VULNERABLE - No PDA validation:**
```rust
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub pool: Account<'info, Pool>,
#[account(mut)]
pub user_stake: Account<'info, UserStake>,
pub user: Signer<'info>,
}
```
**Attack:** User passes a fake `user_stake` account they control with inflated balance.
**SECURE - Validate PDAs:**
```rust
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
mut,
seeds = [b"pool"],
bump = pool.bump
)]
pub pool: Account<'info, Pool>,
#[account(
mut,
seeds = [b"stake", pool.key().as_ref(), user.key().as_ref()],
bump = user_stake.bump,
has_one = user,
has_one = pool
)]
pub user_stake: Account<'info, UserStake>,
pub user: Signer<'info>,
}
```
### 2.2 Incorrect Constraint Ordering
Anchor evaluates constraints in this order:
1. `init` / `init_if_needed` / `mut` / `close`
2. `seeds` and `bump`
3. `has_one`
4. `constraint`
5. Account deserialization
**Implications:**
- Can't use deserialized data in `seeds`
- `constraint` expressions can use deserialized data
- `close` at end ensures account data available for other checks
### 2.3 Over-Reliance on init_if_needed
Covered in section 1.2. Key takeaway: **Avoid `init_if_needed` unless absolutely necessary.**
### 2.4 Missing mut on Accounts
**VULNERABLE - Missing mut:**
```rust
#[derive(Accounts)]
pub struct Deposit<'info> {
pub vault: Account<'info, Vault>, // Missing mut!
pub user: Signer<'info>,
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
ctx.accounts.vault.balance += amount; // Runtime error!
Ok(())
}
```
**SECURE:**
```rust
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
pub user: Signer<'info>,
}
```
### 2.5 PDA Bump Not Using Canonical Bump
**VULNERABLE - Using non-canonical bump:**
```rust
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let (pda, bump) = Pubkey::find_program_address(
&[b"vault"],
ctx.program_id
);
// Storing bump separately is fine, but must validate it
ctx.accounts.vault.bump = bump;
Ok(())
}
// Later, using wrong bump
#[account(
seeds = [b"vault"],
bump = 254 // WRONG - not canonical!
)]
pub vault: Account<'info, Vault>,
```
**SECURE - Always use canonical bump:**
```rust
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = payer,
space = 8 + Vault::INIT_SPACE,
seeds = [b"vault"],
bump // Anchor finds canonical bump
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.vault.bump = ctx.bumps.vault; // Store canonical bump
Ok(())
}
```
### 2.6 Account Reloading After CPI Mutations
**VULNERABLE - Stale account data after CPI:**
```rust
pub fn compound_rewards(ctx: Context<CompoundRewards>) -> Result<()> {
// CPI to claim rewards (mutates user_rewards account)
rewards_program::cpi::claim_rewards(
CpiContext::new(
ctx.accounts.rewards_program.to_account_info(),
ClaimRewards {
user_rewards: ctx.accounts.user_rewards.to_account_info(),
}
)
)?;
// WRONG - using stale data!
let rewards = ctx.accounts.user_rewards.amount;
// Reinvest...
Ok(())
}
```
**SECURE - Reload account after CPI:**
```rust
pub fn compound_rewards(ctx: Context<CompoundRewards>) -> Result<()> {
rewards_program::cpi::claim_rewards(
CpiContext::new(
ctx.accounts.rewards_program.to_account_info(),
ClaimRewards {
user_rewards: ctx.accounts.user_rewards.to_account_info(),
}
)
)?;
// Reload account to get fresh data
ctx.accounts.user_rewards.reload()?;
let rewards = ctx.accounts.user_rewards.amount;
// Reinvest...
Ok(())
}
```
## 3. Anchor CPI Security
### 3.1 Using Program<'info, T> for Program Validation
**VULNERABLE - Using AccountInfo for program:**
```rust
#[derive(Accounts)]
pub struct CallExternal<'info> {
/// CHECK: This is dangerous!
pub external_program: AccountInfo<'info>,
}
```
**SECURE - Using Program<'info, T>:**
```rust
#[derive(Accounts)]
pub struct CallExternal<'info> {
pub external_program: Program<'info, ExternalProgram>,
}
```
`Program<'info, T>` validates:
- Account is executable
- Account owner is BPF Loader
- Account key matches expected program ID
### 3.2 CpiContext Usage Patterns
**Basic CPI:**
```rust
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Transfer};
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
```
### 3.3 with_signer for PDA Signing
**SECURE - PDA signing with CPI:**
```rust
pub fn transfer_from_vault(ctx: Context<TransferFromVault>, amount: u64) -> Result<()> {
let authority_bump = ctx.accounts.vault.authority_bump;
let authority_seeds = &[
b"vault-authority",
&[authority_bump]
];
let signer_seeds = &[&authority_seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.vault_token_account.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.vault_authority.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new_with_signer(
cpi_program,
cpi_accounts,
signer_seeds // PDA can now sign!
);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
```
### 3.4 Validating CPI Return Data
**SECURE - Check CPI return values:**
```rust
pub fn safe_cpi_call(ctx: Context<SafeCpiCall>) -> Result<()> {
let result = external_program::cpi::risky_operation(
CpiContext::new(
ctx.accounts.external_program.to_account_info(),
RiskyOperation { /* ... */ }
)
)?;
// Validate return data
require!(
result.get().success,
ErrorCode::CpiOperationFailed
);
Ok(())
}
```
### 3.5 Avoiding Arbitrary CPI Targets
**VULNERABLE - Arbitrary CPI target:**
```rust
#[derive(Accounts)]
pub struct ArbitraryCpi<'info> {
/// CHECK: DANGEROUS - allows any program!
pub target_program: AccountInfo<'info>,
}
```
**SECURE - Constrained CPI targets:**
```rust
#[derive(Accounts)]
pub struct SafeCpi<'info> {
// Option 1: Type-safe program constraint
pub token_program: Program<'info, Token>,
// Option 2: Explicit allowlist
#[account(
constraint = allowed_programs.contains(&other_program.key())
@ ErrorCode::UnauthorizedProgram
)]
pub other_program: Program<'info, OtherProgram>,
}
```
## 4. Account Type Safety
### 4.1 Account Discriminators
Anchor automatically adds an 8-byte discriminator to each account type (first 8 bytes of SHA256 hash of `"account:<AccountName>"`).
**How it protects you:**
```rust
#[account]
pub struct Vault {
pub authority: Pubkey,
pub balance: u64,
}
#[account]
pub struct UserAccount {
pub authority: Pubkey,
pub balance: u64,
}
// Anchor prevents this type confusion:
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>, // Won't deserialize UserAccount!
}
```
**Manual discriminator handling:**
```rust
impl Vault {
pub const DISCRIMINATOR: [u8; 8] = [/* computed at compile time */];
}
// Checking discriminator manually
let discriminator = &data[0..8];
require!(
discriminator == Vault::DISCRIMINATOR,
ErrorCode::InvalidAccountType
);
```
### 4.2 Account<'info, T> vs AccountInfo
**Account<'info, T>:**
- Type-safe deserialization
- Automatic discriminator check
- Automatic owner check
- Immutable/mutable access control
**AccountInfo:**
- Raw account data
- No automatic validation
- Use only when necessary (non-Anchor programs, dynamic account types)
**VULNERABLE - Using AccountInfo unnecessarily:**
```rust
#[derive(Accounts)]
pub struct UpdateVault<'info> {
/// CHECK: Missing type safety!
pub vault: AccountInfo<'info>,
}
```
**SECURE - Use Account<'info, T>:**
```rust
#[derive(Accounts)]
pub struct UpdateVault<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
}
```
### 4.3 AccountLoader for Zero-Copy Accounts
For large accounts (>10KB), use zero-copy deserialization:
```rust
#[account(zero_copy)]
pub struct LargeAccount {
pub data: [u8; 100000],
}
#[derive(Accounts)]
pub struct UpdateLargeAccount<'info> {
#[account(mut)]
pub large_account: AccountLoader<'info, LargeAccount>,
}
pub fn update(ctx: Context<UpdateLargeAccount>) -> Result<()> {
let mut account = ctx.accounts.large_account.load_mut()?;
account.data[0] = 42;
Ok(())
}
```
**Security note:** Zero-copy accounts use `RefCell` internally. Must call `load()` or `load_mut()` each time you access data to ensure safety.
### 4.4 Type Cosplay Prevention
**Attack:** Creating fake accounts with correct discriminator but wrong program owner.
**Anchor's defense:**
```rust
#[account]
#[derive(Default)]
pub struct MyAccount {
pub data: u64,
}
// Anchor checks:
// 1. Discriminator matches
// 2. Owner is this program's ID
// 3. Account is properly sized
```
**Additional validation for external accounts:**
```rust
#[derive(Accounts)]
pub struct UseExternalAccount<'info> {
#[account(
constraint = external_account.owner == &external_program::ID
@ ErrorCode::InvalidAccountOwner
)]
pub external_account: AccountInfo<'info>,
}
```
## 5. Error Handling Security
### 5.1 Custom Error Codes
**Define clear error codes:**
```rust
#[error_code]
pub enum ErrorCode {
#[msg("Insufficient funds for withdrawal")]
InsufficientFunds,
#[msg("Unauthorized access attempt")]
Unauthorized,
#[msg("Invalid configuration parameters")]
InvalidConfig,
#[msg("Arithmetic overflow occurred")]
Overflow,
}
```
**Use with require! macro:**
```rust
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
require!(
ctx.accounts.vault.balance >= amount,
ErrorCode::InsufficientFunds
);
require!(
ctx.accounts.vault.authority == ctx.accounts.user.key(),
ErrorCode::Unauthorized
);
// Safe to proceed
Ok(())
}
```
### 5.2 Error Propagation Patterns
**WRONG - Silencing errors:**
```rust
pub fn risky_operation(ctx: Context<RiskyOp>) -> Result<()> {
let _ = dangerous_function(); // WRONG - error silenced!
Ok(())
}
```
**CORRECT - Propagate errors:**
```rust
pub fn risky_operation(ctx: Context<RiskyOp>) -> Result<()> {
dangerous_function()?; // Propagate error
Ok(())
}
```
### 5.3 Avoiding Silent Failures
**VULNERABLE - No error on failure:**
```rust
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
if ctx.accounts.from.balance >= amount {
ctx.accounts.from.balance -= amount;
ctx.accounts.to.balance += amount;
}
// Returns Ok even if transfer didn't happen!
Ok(())
}
```
**SECURE - Explicit error:**
```rust
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
require!(
ctx.accounts.from.balance >= amount,
ErrorCode::InsufficientFunds
);
ctx.accounts.from.balance -= amount;
ctx.accounts.to.balance += amount;
Ok(())
}
```
## 6. Token Program Integration
### 6.1 anchor_spl Security Patterns
**SECURE - Using anchor_spl helpers:**
```rust
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
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,
)?;
Ok(())
}
```
### 6.2 token_interface Usage
For Token-2022 compatibility:
```rust
use anchor_spl::token_interface::{self, TokenInterface, TokenAccount};
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub from: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub to: InterfaceAccount<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Interface<'info, TokenInterface>,
}
```
### 6.3 Associated Token Account Constraints
**VULNERABLE - Missing ATA validation:**
```rust
#[derive(Accounts)]
pub struct DepositTokens<'info> {
#[account(mut)]
pub user_token_account: Account<'info, TokenAccount>,
pub user: Signer<'info>,
}
```
**SECURE - Validate ATA:**
```rust
use anchor_spl::associated_token::AssociatedToken;
#[derive(Accounts)]
pub struct DepositTokens<'info> {
#[account(
mut,
associated_token::mint = mint,
associated_token::authority = user
)]
pub user_token_account: Account<'info, TokenAccount>,
pub user: Signer<'info>,
pub mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
```
### 6.4 Token-2022 Extension Handling
**Be aware of extensions:**
```rust
pub fn handle_transfer(ctx: Context<HandleTransfer>, amount: u64) -> Result<()> {
// Token-2022 may have transfer fees, freeze authority, etc.
// Always check actual amount received after transfer
let before_balance = ctx.accounts.destination.amount;
token_interface::transfer_checked(
CpiContext::new(/* ... */),
amount,
ctx.accounts.mint.decimals,
)?;
ctx.accounts.destination.reload()?;
let actual_amount = ctx.accounts.destination.amount - before_balance;
// Use actual_amount for accounting
Ok(())
}
```
## 7. Event Security
### 7.1 When to Emit Events
Events are critical for:
- Indexing and querying program state
- Auditing sensitive operations
- Monitoring for security incidents
**Always emit events for:**
- State changes (deposits, withdrawals, config updates)
- Authorization changes (role grants, ownership transfers)
- Critical operations (program upgrades, emergency actions)
### 7.2 Event Data Validation
**SECURE - Validate before emitting:**
```rust
#[event]
pub struct WithdrawalEvent {
pub user: Pubkey,
pub amount: u64,
pub timestamp: i64,
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
// Validate first
require!(
ctx.accounts.vault.balance >= amount,
ErrorCode::InsufficientFunds
);
// Perform operation
ctx.accounts.vault.balance -= amount;
// Emit event AFTER successful operation
emit!(WithdrawalEvent {
user: ctx.accounts.user.key(),
amount,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
```
### 7.3 emit! vs emit_cpi!
**emit! - Regular event:**
```rust
emit!(MyEvent {
data: value,
});
```
**emit_cpi! - Event for CPI callers:**
```rust
// Use when program is called via CPI and event should be
// visible to the calling program
emit_cpi!(MyEvent {
data: value,
});
```
## 8. Anchor-Specific Best Practices
### 8.1 Account Space Calculation with InitSpace
**SECURE - Using InitSpace derive macro:**
```rust
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct UserProfile {
pub authority: Pubkey, // 32 bytes
#[max_len(50)]
pub name: String, // 4 + 50 bytes
pub created_at: i64, // 8 bytes
pub bump: u8, // 1 byte
}
#[derive(Accounts)]
pub struct CreateProfile<'info> {
#[account(
init,
payer = payer,
space = 8 + UserProfile::INIT_SPACE, // 8 for discriminator
seeds = [b"profile", authority.key().as_ref()],
bump
)]
pub profile: Account<'info, UserProfile>,
pub authority: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
```
### 8.2 Remaining Accounts Handling
**SECURE - Validate remaining accounts:**
```rust
pub fn process_multiple_accounts(
ctx: Context<ProcessAccounts>,
count: u8,
) -> Result<()> {
let remaining = &ctx.remaining_accounts;
// Validate count
require!(
remaining.len() == count as usize,
ErrorCode::InvalidAccountCount
);
// Validate each account
for account_info in remaining.iter() {
require!(
account_info.is_writable,
ErrorCode::AccountNotWritable
);
require!(
account_info.owner == ctx.program_id,
ErrorCode::InvalidAccountOwner
);
// Deserialize and validate type
let account = Account::<MyAccount>::try_from(account_info)?;
// Process account...
}
Ok(())
}
```
### 8.3 Instruction Data Validation
**SECURE - Validate all inputs:**
```rust
pub fn create_proposal(
ctx: Context<CreateProposal>,
title: String,
description: String,
execution_delay: i64,
) -> Result<()> {
// Validate string lengths
require!(
title.len() > 0 && title.len() <= 100,
ErrorCode::InvalidTitleLength
);
require!(
description.len() <= 1000,
ErrorCode::DescriptionTooLong
);
// Validate numeric ranges
require!(
execution_delay >= MIN_DELAY && execution_delay <= MAX_DELAY,
ErrorCode::InvalidExecutionDelay
);
// Validate against overflow
let execution_time = Clock::get()?
.unix_timestamp
.checked_add(execution_delay)
.ok_or(ErrorCode::Overflow)?;
ctx.accounts.proposal.title = title;
ctx.accounts.proposal.description = description;
ctx.accounts.proposal.execution_time = execution_time;
Ok(())
}
```
### 8.4 Upgradability Considerations
**SECURE - Handle program upgrades safely:**
```rust
#[account]
#[derive(InitSpace)]
pub struct ProgramConfig {
pub version: u8,
pub upgrade_authority: Pubkey,
pub paused: bool,
}
pub fn migrate(ctx: Context<Migrate>) -> Result<()> {
let config = &mut ctx.accounts.config;
// Check current version
require!(
config.version < CURRENT_VERSION,
ErrorCode::AlreadyMigrated
);
// Perform version-specific migrations
match config.version {
0 => {
// Migrate from v0 to v1
// Add new fields, transform data, etc.
}
1 => {
// Migrate from v1 to v2
}
_ => return Err(ErrorCode::UnsupportedVersion.into()),
}
config.version = CURRENT_VERSION;
Ok(())
}
```
**Emergency pause pattern:**
```rust
#[derive(Accounts)]
pub struct SensitiveOperation<'info> {
#[account(
constraint = !config.paused @ ErrorCode::ProgramPaused
)]
pub config: Account<'info, ProgramConfig>,
// ... other accounts
}
```
This ensures you can pause the program in case of emergencies during or after upgrades.