TL;DR

  • What you’ll learn: How to produce an auditor-friendly SVM program architecture document.

  • Why it matters: A clear, well-structured doc speeds up auditor onboarding and improves audit outcomes.

  • Exo Edge: This is our battle-tested workflow — used successfully across multiple audit collaborations.

Introduction

Audit preparation is often treated as a checklist at the end of development — but in practice, the way you document your program can determine how quickly (and correctly) auditors understand your design intent.

At Exo, we’ve now built and refined a repeatable architecture documentation approach that speeds up and improves the reliability of onboarding auditors. Throughout the development of this guide, we collaborated with the audit team at Accretion, who provided direct commentary and practical insights drawn from real-world Solana audits.

Their feedback highlighted the importance of clear business logic, explanations of permissioning, user flows, and the explicit intent behind financial code paths. Those contributions are included throughout this guide in the form of pull quotes and best-practice clarifications, helping ensure this isn’t just theory but a resource aligned with the realities of institutional-grade audit work.

If your team is preparing for an audit — or planning ahead — this guide will help you give auditors the right information, in the right format, at the right time.

Onboarding Auditors Is Often an Afterthought

Many teams don’t consider how their code will be presented to auditors until the process begins. However, onboarding auditors efficiently can drastically improve the quality and speed of the review.

When auditors encounter code without context, questions arise:

  • “What does this variable mean?”

  • “Is this critical?”

  • “In what order are those instructions supposed to be called?”

  • “Is it intentional that more fees are charged when you swap A>B vs B>A”

  • “Why are there 4 different mint instructions?”

These answers aren’t always clear to the auditor until they have spent a lot of time analyzing the code. That’s why a dedicated architecture document is so valuable.

Why an Architecture Document Matters

Creating an architecture document forces you to express your program’s purpose and design rationale clearly.

This process:

  • Helps auditors understand your logic faster, reducing onboarding time.

  • Surfaces design weaknesses early, improving your own comprehension of the program.

The benefit is twofold: auditors onboard faster, and developers strengthen their understanding.

User flows, descriptions of business logic, and clear explanations for accounts and instructions are the most useful things for us to quickly understand the program.

Robert (r0bre), Accretion

Describing the Smart Contract

Start by giving a high-level overview of what the contract does and its main features.

Answer key questions up front:

  • Who are the users?

  • Where does the liquidity come from?

  • What tokens are supported? (if applicable)

  • What extensions are supported? (if applicable)

[As an example] fees always kinda work similar, but it's important to know how exactly the dev wants fees to be levied, so we can tell if the code does exactly that.

Robert (r0bre), Accretion

Glossary

Include a glossary of all terms used in your program.

Example:

Term

Description

Parlay Bet

Combines multiple bets in one

Point Spread

The expected final score difference between two teams

Dutch Auction

A method of selling where the auctioneer starts with a very high price and gradually lowers it until a bidder accepts the price

Role-Based Access Control (RBAC)

If your program implements RBAC, enumerate the roles hierarchically.

Example Role Hierarchy

  • Global Admin

  • Manager

  • User

Then, detail the permissioning pipeline — who can grant what role:

  • Only a Global Admin can grant the Manager role.

  • Only a Manager can whitelist users.

This ensures clarity when auditors evaluate access pathways.

User Flows

Outline the most important user flows, especially those involving account initialization or fund movement.

Example:

Initialize Pool and RBAC 
1. check mint order
2. init pool
3. init RBAC
Swap
1. Get amount out
2.transfer fee to fee recipient
3. transfer user token to reserve
4. transfer reserve token to user

These summaries give auditors a quick, intuitive understanding of transaction paths.

Handling State

Next, define how state is managed and list the key accounts included.

Example:

// This account acts as a global whitelist
// PDA ["global_config"]
// There will be only one
pub struct GlobalConfig {
	// This is the super admin
	pub admin: Pubkey
	// These are the allowed users that can manage
	#[max_len(5)]
	pub managers: Vec<Pubkey>
	pub bump: u8
}

// PDA ["pool_config", mint]
//There should be exactly one config for each market
pub struct PoolConfig {
	// This is the pool admin
	pub admin: Pubkey
	// The token mint that will be used for this pool
	pub mint: Pubkey
	pub bump: u8
}

A short explanation like this provides immediate clarity on the account's purpose and relationships. Additionally, if there are any sequential enums, it would be beneficial to include them here as well.

E.g. The order state account has the following states:

#[derive(Clone, AnchorSerialize, AnchorDeserialize, anchor_lang::InitSpace, PartialEq)]
#[repr(u8)]
pub enum OrderStateEnum {
    Initialized,
    Raising,
    Refunding,
    Refunded,
    Completed,
}

The expected flow for this is:

  • Initialized → Raising → Completed

  • Initialized → Raising → Refunding → Refunded

Handling Instructions

Once state is clear, describe the external functions (instructions) of your program.

For each instruction, include:

  1. Name of Instruction (and RBAC requirement)

  2. Purpose: What it does

  3. Pre-conditions: What it checks before execution (roles, permissions, etc.)

  4. Arguments:

    Args: Arg_one: Pubkey

  5. Post-conditions: What happens after execution

While EVM commonly uses multiple interacting contracts, we generally favor a monolithic program approach due to Solana's CPI depth limitations. However, we will often group instructions based on permissioning or the state it mutates to make it easier to read.

Critical Code

Highlight any functions that handle sensitive or high-impact logic, such as fund management.

Example:

pub fn get_shares_from_assets(
    shares_mint: &InterfaceAccount<'_, Mint>,
    shares_amount: u64,
    total_assets: u64
    rounding: Rounding,
) -> Result<u64> {
    let assets_times_total_supply =
        u128::from(shares_mint.supply.checked_add(1).expect("overflow"))
            .checked_mul(u128::from(shares_amount))
            .ok_or(ArchitectureProgramError::ArithmeticError)?;
    let result = match rounding {
        Rounding::Up => assets_times_total_supply.div_ceil(u128::from(
            total_assets.checked_add(1).expect("overflow"),
        )),
        Rounding::Down => assets_times_total_supply
            .checked_div(u128::from(total_assets.checked_add(1).expect("overflow")))
            .ok_or(ArchitectureProgramError::ArithmeticError)?,
    };
    u64::try_from(result).or(Err(ArchitectureProgramError::ArithmeticError.into()))
}

Call out the purpose and risk factors here (e.g. rounding, overflow checks, arithmetic safety).

Preparing Your Code for Audit Review (What auditors expect)

Confirm the project builds clean from a fresh machine on both Mac M-chip and Linux x86. Document exact setup steps — not just ‘install Rust.

Robert (r0bre), Accretion
  • Confirm clean build instructions with exact tool versions

  • Tests must pass and include failure scenarios

  • Include simulation-style flows for multiple users

  • Provide adversarial tests (what happens when things fail?)

Write lots of tests. The product should work — we can’t audit something that doesn’t.

Robert (r0bre), Accretion

General Code Style Guidelines

1. Add Docstrings to Structures

Example:

// PDA ["Oracle", wallet.as_ref()]
// There will be an oracle for each mint
#[account]
#[derive(InitSpace)]
struct Oracle {
	/// Price to buy 1*10^decimals units of the asset in USDC
	price: u64,
	mint: Pubkey,
	/// Authority who can update the price
	authority: Pubkey,
	/// Slot in which the Price was quoted
	posted: u64,
	decimals: u8,
}

Comments and docstrings are necessary for clarifying intent — hovering in an IDE should tell us what something means.

Robert (r0bre), Accretion

2. Document Instructions Clearly

These doc comments may be included into auto generated clients and are therefore very important for future integrators as well as the auditors!

/// Add a deployer (This role allows you to create a fund) (Role: GLOBAL_ADMIN)
pub fn add_deployer(ctx: Context<AddDeployer>, deployer: Pubkey) -> Result<()> {
    instructions::add_deployer::handler(ctx, deployer)
}

3. Comment Math Blocks

Explain intent before performing calculations.

/// avg = (a + b) / 2
pub fn avg(a: u64, b:u64) -> u64 {}

4. Maintain a Multi-File Layout

Separate state and instructions into distinct folders, ideally one file per module.

This modular approach improves readability and auditability.

It’s always good to structure the code in multiple files. It helps the brain and makes git easier.

Robert (r0bre), Accretion
  1. Build / Setup

If the project only builds on Mac or only on x86, it would be good to let the auditor know this so it won’t waste an hour trying to run it on their setup.

Continuous Improvement

We’re still refining how we onboard auditors to our projects, but this workflow represents our latest and most effective iteration.

By documenting architecture early, teams build transparency and make life easier for both internal reviewers and external auditors.

Conclusion

Architecture is a fundamental pillar of software development. It not only clarifies design decisions but also encourages critical debate and shared understanding. A detailed architecture doc ensures everyone — from devs to auditors — understands how and why the system works as it does. This process isn’t theoretical; it’s our internal workflow, refined across multiple audits.

The payoff: faster onboarding, higher-quality findings, and stronger developer understanding of their own codebase.

Special thanks to Accretion’s auditors — namely, Robert — for sharing actionable insights that helped shape this guide.

Resources

Reply

or to participate

Keep Reading

No posts found