Solana and Anchor coding standards analysis with convention scoring
curl -s https://parity.cx/api/skills/best-practices | shcopyYou are evaluating a Solana smart contract against Anchor and Solana development best practices. Unlike the security-audit skill, this analysis focuses on code quality, maintainability, and adherence to framework conventions rather than exploitable vulnerabilities. Findings from this skill are typically medium or info severity but indicate code that is harder to maintain, more error-prone, or deviates from community standards.
Before starting analysis, fetch the following from the Parity repository to use as reference patterns:
| Source | Path | Contains |
|--------|------|----------|
| Framework Patterns | programs/parity/src/context_engine.rs > ANCHOR_PATTERNS | Correct Anchor patterns for account init, PDA derivation, CPI, access control, error handling, close, events, and checked math |
| Skill Definition | programs/parity/src/skills.rs > BUILTIN_SKILLS (best-practices entry) | Canonical step list and input/output spec for this skill |
GitHub base URL: https://github.com/paritydotcx/Skills/blob/main/
Compare the target program's patterns against ANCHOR_PATTERNS to determine conformance. Each pattern entry has pattern_name, description, and example_code showing the correct implementation.
Read the entire program source and catalog:
#[derive(Accounts)] structs and their constraints#[account] definitions (stored account types)#[error_code])#[event])msg! logging macroCheck whether stored account types use InitSpace derive for automatic space calculation.
What to look for:
#[derive(InitSpace)] on all #[account] structsspace values in init constraints (fragile, breaks when fields change)8 + MyAccount::INIT_SPACE pattern in init constraints (correct usage)Suboptimal pattern:
// BAD: hardcoded space breaks silently when fields are added
#[account(init, payer = user, space = 8 + 32 + 8 + 1)]Recommended pattern:
#[derive(InitSpace)]
pub struct MyAccount {
pub authority: Pubkey, // 32
pub balance: u64, // 8
pub is_active: bool, // 1
}
#[account(init, payer = user, space = 8 + MyAccount::INIT_SPACE)]Finding if violated:
Check that the program defines descriptive custom errors.
What to look for:
#[error_code] enum with named variantsrequire! or require_keys_eq! macros instead of if-else with raw error returnsOk(()) without validating input preconditionsSuboptimal pattern:
// BAD: error name alone is not informative in transaction logs
#[error_code]
pub enum MyError {
Unauthorized,
InvalidInput,
}Recommended pattern:
#[error_code]
pub enum MyError {
#[msg("Caller is not the program authority")]
Unauthorized,
#[msg("Deposit amount must be greater than zero")]
InvalidDepositAmount,
#[msg("Withdrawal exceeds available balance")]
InsufficientBalance,
}Finding if violated:
Every state-changing instruction should emit an event for off-chain indexing.
What to look for:
#[event] struct definitions for significant state changes (deposits, withdrawals, transfers, config updates, account creation/closure)emit!() calls at the end of instruction handlersSuboptimal pattern:
// BAD: state changes with no event emission
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
ctx.accounts.vault.balance += amount;
Ok(())
}Recommended pattern:
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
ctx.accounts.vault.balance = ctx.accounts.vault.balance
.checked_add(amount)
.ok_or(ErrorCode::Overflow)?;
emit!(DepositEvent {
user: ctx.accounts.user.key(),
vault: ctx.accounts.vault.key(),
amount,
new_balance: ctx.accounts.vault.balance,
});
Ok(())
}Finding if violated:
Check that all account references use typed Anchor wrappers instead of raw AccountInfo.
What to look for:
AccountInfo<'info> used where Account<'info, T>, Program<'info, T>, Signer<'info>, or SystemAccount<'info> would be appropriateUncheckedAccount used without a documented reason (comment explaining why)Finding if violated:
Evaluate whether instruction handlers follow the single-responsibility principle.
What to look for:
Finding if violated:
Check for diagnostic msg! logging in instruction handlers.
What to look for:
msg! calls that log the instruction name and key parameters at entryFinding if violated:
Check that magic numbers and repeated values are extracted to named constants.
What to look for:
Finding if violated:
Calculate a best-practices score from 0 to 100:
| Severity | Penalty per finding |
|----------|-------------------|
| Medium | -8 |
| Info | -3 |
Start at 100. Subtract penalties. Floor at 0.
Return results as structured JSON:
{
"score": 82,
"findings": [
{
"severity": "medium",
"title": "Missing event emission on withdraw",
"location": {
"file": "lib.rs",
"line": 73,
"instruction": "withdraw"
},
"description": "The withdraw instruction modifies vault balance without emitting an event.",
"recommendation": "Add a WithdrawEvent struct and emit it with user, vault, amount, and remaining balance fields."
}
],
"summary": "0 critical, 0 high, 2 medium, 3 info findings. Code is functional but would benefit from event emission and typed wrappers."
}