Back to blog

Smart Contract Security in Practice

Lessons from Building on Solana with Anchor

Solana has become the go-to ecosystem for high-performance on-chain applications - from liquidity protocols to NFT marketplaces and advanced token mechanics. But building secure programs on Solana isn’t trivial. With a massively parallel runtime, shared global state, and strict account rules, security is a discipline, not just a checklist.

At KROK, we’ve delivered numerous Solana-based products - complex token systems, liquidity market makers, multi-account state machines, marketplaces, gaming logic, and more. Over time, our team developed a practical, repeatable security workflow that has helped us ship safely and confidently.

This article summarizes some rules we use on every production Solana project. Semi-technical, battle-tested, and grounded in experience.

1. Security Starts Before Code: Spec + Pre-Audit

Most Solana exploits are architectural. Not a Rust bug. Not a missing check. But incomplete thinking at the specification stage. Before development begins, every KROK project includes:

1.1 Detailed Technical Specification

Covering:

  • PDA derivation + namespace design

  • Account models, sizes, mutability rules

  • Access control & roles

  • State machine diagrams

  • Invariants (“this value can never decrease”, etc.)

  • Gas/compute considerations

This ensures everyone understands how the system must behave before anyone writes an instruction handler.

1.2 Pre-Audit Threat Analysis

Before coding, we map:

  • possible attack surfaces

  • privilege escalation paths

  • reinitialization vectors

  • PDA collisions

  • state desynchronization risks

Thinking like an attacker early can help reduce some potential future issues.

2. Getting Account Handling Right (The #1 Source of Bugs)

Solana is unique: accounts are the program’s entire world. Which means most vulnerabilities come from improper account usage. Some of the practices we enforce internally at KROK:

2.1. Pay Special Attention to Accounts Passed Into Instructions

Every instruction can be called by anyone, with any accounts, in any order. This is why account validation is the most important part of every handler.

Anchor helps with:

  • correct deserialization

  • correct owner checks

  • preventing account substitution

  • signer validation

  • ensuring writable vs. read-only contracts

But Anchor constraints don’t cover everything, so we always reinforce them with logic checks.

Example:

#[account(

    mut,

    has_one = authority,

    seeds = [b"vault", authority.key().as_ref()],

    bump

)]

pub vault: Account<'info, Vault>;

And then:

let (expected, _) = Pubkey::find_program_address(

    &[b"vault", authority.key().as_ref()],

    ctx.program_id,

);

require!(expected == vault.key(), CustomError::InvalidVaultPDA);

Rule:

Always assume the caller is malicious. Never trust the accounts they pass you.

3. Use Safe Math — Always

Solana programs operate on:

  • fixed-point decimals

  • big integers

  • token amounts

  • fee multipliers

  • liquidity math

  • bit shifts and ratios

A single overflow can break your invariant and freeze your protocol. We enforce use of:

checked_add

checked_sub

checked_mul

checked_div

checked_shl

Example:

let new_balance = vault

    .balance

    .checked_sub(amount)

    .ok_or(CustomError::Underflow)?;

Why this matters:

Arithmetic safety is one of the easiest ways to prevent catastrophic logic breaks - especially in DeFi-like programs.

4. Minimize Serialization & Deserialization Risk

Incorrect serialization is a silent killer in Solana development. To prevent this:

  • we rely entirely on Anchor for serialization/deserialization

  • we avoid manual byte manipulation unless absolutely necessary

  • we prioritize fixed-size layouts over variable ones

Dynamic layouts easily lead to:

  • misaligned offsets

  • corrupted state after upgrades

  • unexpected data padding

  • hidden attack vectors

Using stable, fixed layouts makes the entire system more predictable and auditable.

5. Validate All Inputs Extensively

Inputs are untrusted. Accounts are untrusted. Everything is untrusted.

After decoding state, the program must verify all fields:

Examples:

  • amounts must be positive and within expected bounds

  • timestamps must satisfy logical constraints

  • accounts must be in the correct relationship to each other

  • fee values must follow protocol rules

  • states must be valid for the current instruction

If your program processes user funds, assume every input is designed to break you.

6. Defensive Logical Design: State Machines & Rigid Flows

A defensive mindset prevents a whole category of vulnerabilities. At KROK, we use patterns such as:

State machine pattern

Each object can only be in one valid state at a time.

Example:

enum SaleState {

    Created,

    Active,

    Completed,

    Cancelled,

}

Instructions must enforce valid transitions only:

require!(sale.state == SaleState::Active, CustomError::InvalidState);

Inversion of defaults

Start with the assumption that nothing is valid until proven valid.

Zero-trust instruction design

Treat every instruction call as hostile unless verified otherwise.

7. Test Everything: Unit, Integration, Fuzzing

Our internal rule:

  • > 90% code coverage

  • 100% instruction coverage

  • All branches tested

  • Negative tests mandatory

  • Fuzz Testing

Anchor provides fuzzing tools that help identify:

  • arithmetic boundary failures

  • unintuitive state transitions

  • invalid orderings of accounts

  • branching logic anomalies

If it touches state, it must be tested.

8. AI-Assisted Exploit Discovery

One of our internal innovations at KROK is structured AI-driven testing.

We use AI to attempt:

  • invalid account graphs

  • PDA collision attempts

  • reordered accounts

  • privilege escalation scenarios

  • edge-case liquidity/math attacks

AI doesn’t replace audits -it helps discover high-entropy attack attempts humans wouldn’t think of.

9. External Audit Before Any Mainnet Launch

Every contract undergoes a fresh external audit done by 3rd part company not included into development process..

Auditors check:

  • spec + implementation mismatch

  • invalid or missing invariants

  • account substitution vectors

  • access control & signer logic

  • financial attack surfaces

  • PDA namespace collisions

  • rent/space inconsistencies

  • upgradeability risks

Only after all findings are fixed do we deploy. Even simple projects get this treatment - because attackers don’t care about scope.

10. A Continuous Security Mindset

Security isn’t a stage. It’s a philosophy.

On Solana - a high-performance, low-latency chain - even a small oversight can lead to a catastrophic exploit. A safe program requires:

  1. Secure design

  2. Secure coding

  3. Aggressive testing

  4. Independent auditing

  5. Continuous monitoring

This mindset has allowed us at KROK to build and maintain multiple Solana production systems with strong reliability and zero critical incidents.

Whether you're building a token system, liquidity engine, NFT infrastructure, or any other on-chain logic, our team at KROK brings deep Solana/Anchor experience, strict internal processes, and production-tested engineering practices.