DeFi Security Alliance

Chains

Solana Smart Contract Audit: Anchor, PDAs, CPI and Token-2022 Scope

Commission a Solana smart contract audit around account relationships, delegated privileges and the token behavior your program accepts. Our source-registry scan classified 28 Token-2022 extension types, showing why a program-ID check alone cannot define integration scope. Bring a pinned build and an explicit acceptance policy to the reviewer.

Lattice of translucent nodes plugged into keyed sockets with one blue key turning, representing the scope of a Solana program audit.

Key facts

Original extension inventory
28 non-test, non-padding Token-2022 types: 19 mint types and 9 token-account types
Initialization dependency
4 of 19 mint extension types require token-account extensions in the sampled source
Exclusions
32 enum variants minus 3 test variants and 1 padding sentinel; no unclassified included types
Authorization boundary
Signer status, account ownership and application authority are different checks
Measurement limit
Source-registry presence does not establish activation or support on a target cluster

Which EVM assumptions stop holding

A Solana smart contract audit reviews the program together with the accounts and authorities its instructions accept. Reading the instruction handler alone leaves much of the trust boundary unexplained. The review brief should identify who supplies each account, which relationships make it valid and what privileges the program forwards to another program.

Solana's account model separates executable programs from stored state. The account's owner program controls modification of its data. That owner field is not a general statement about which human controls the asset represented inside the account. Keep the runtime owner, the application authority and the transaction's signer status distinct.

Rust's type system also does not establish the application's authorization policy. A value can deserialize correctly while referring to another user's state or an unintended market. The missing check is a relationship: the supplied authority must match the account's designated authority, and the selected vault must belong to the selected market.

Questions that change when scoping a Solana program review
Review dimensionEvidence to requestFailure to test
Account identityExpected addresses and derivation rulesA validly typed substitute account
Authority relationshipStored authority and required signer conditionAn unrelated signer authorizing another user's action
Program dependencyAllowed callee IDs and forwarded privilegesAn unintended program receiving signing authority
Token behaviorAccepted mint and account extensionsA transfer succeeding with unexpected received value or restrictions

For an EVM migration, ask the reviewer to rewrite these assumptions explicitly. A familiar contract function name does not establish the same calling or storage model on another runtime.

Our registry scan classified 28 extension types

Token-2022 support needs a defined acceptance policy. To establish a reproducible starting inventory, we enumerated the extension registry in the official token program repository and classified each included type by the account on which it belongs. We also extracted the mint-to-account initialization requirements defined in that source.

Method

Source
The ExtensionType enum and its account-type mapping in interface/src/extension/mod.rs of the official Token-2022 repository, at a pinned commit.
Retrieved
. The result stores the commit, source and endpoint outcomes.
Population
32 enum variants before exclusions. We removed 3 test-only variants and the Uninitialized padding sentinel, leaving 28 types.
Classification
Use the source's account-type match arms and its required-account-extension mappings. No inference from extension names and no manual vulnerability labeling.

Results

Token-2022 source registry on
ObservationCountDenominatorAudit use
Mint extension types1928 included typesInspect mint-level behavior and authorities
Token-account extension types928 included typesInspect the actual source and destination accounts
Mint types requiring account extensions419 mint typesCheck initialization dependencies
Unclassified types028 included typesThe extracted mapping covers the sampled registry

The required-account mappings cover transfer fees, non-transferability, transfer hooks and pausing in this source revision. Non-transferability maps to both its account marker and immutable ownership. That dependency means a mint-only inventory can miss account initialization requirements even before application-specific policy is considered.

Use the registry as a comparison set, not an allowlist. For each extension your program accepts, record why the behavior is supported and which tests establish that support. For everything else, define rejection behavior. An unexpected extension should not silently inherit compatibility from an ordinary transfer test.

Limits

  • Source presence is not proof of activation on a target cluster.
  • The snapshot does not establish wallet support, deployed token prevalence or whether every possible extension combination is valid. Those questions require the target release and integration tests.
  • Required initialization mappings are only part of token behavior. Account-level settings can still change what a later transfer permits.
  • No program was compiled or audited by this census. Its complete classification is a data-inventory result, not a security certification.

The script seo/research/solana-extension-survey.py and result seo/research/solana-extension-survey-2026-09-05.json are kept in the repository and are not published as site downloads.

Signer and owner checks

A signer check asks whether an account has signing privilege for the invocation. Authorization asks whether that account is the authority the application requires. Test both. Supplying a real signature from an unrelated wallet should not let a caller withdraw from another user's vault.

Anchor's account-type reference distinguishes checked account containers from UncheckedAccount. Its constraint reference provides separate checks for signer status, ownership, fixed addresses and stored relationships. Choosing a checked type does not make every remaining business relationship automatic.

Signer
Establish signing privilege, then bind that signer to the required authority.
Account
Use the appropriate data type and ownership validation, then verify the account's relation to the requested operation.
has_one
Check a stored key against the corresponding supplied account where that relationship defines authorization.
UncheckedAccount
Require explicit validation in the handler or a documented reason why no additional trust is placed in the account.

Prepare substitution tests. Replace the vault with another valid vault, substitute a mint from a different market and pass an authority that signs correctly but is not stored in the state. A deserialization failure is useful for malformed input; it does not exercise a well-formed but unauthorized substitute.

Review account aliasing against the pinned Anchor version. If the same account appears in distinct logical roles, establish whether the framework rejects it or the instruction permits it deliberately. Where duplicates are accepted, test the resulting state changes. Do not rely on an old framework assumption when the current constraint behavior differs.

For token operations, retain the distinction between the runtime owner program and authorities stored in token data. Then check the mint and token-program identity together. The audit should be able to explain why the supplied source account, destination account and mint are valid for this operation without relying on the front end to choose them correctly.

PDA seeds and canonical bumps

A PDA derives an address from a program ID and seeds without creating a private key. Solana's PDA documentation explains how the deriving program can obtain signing privilege through the runtime. Derivation and ownership are different concepts: the program that derives an address need not be the owner program of every account placed at that address.

The canonical-bump rule chooses the expected address from valid derivations. If the application assumes a unique account for a user and purpose, accepting another valid bump can break that uniqueness assumption. The audit must identify where the canonical value is established and how later instructions verify the same account.

  1. List each PDA's purpose and seed schema.
  2. Bind user-scoped state to the intended user and market context. Review ambiguous seed construction and whether distinct logical inputs could select the same namespace.
  3. Check initialization and subsequent access together. A stored bump is useful only if the state carrying it was initialized and authenticated under the intended rule.
  4. Test substitution across users, markets and deployment IDs. Keep the expected derived address beside each test input so the reviewer can inspect which boundary was exercised.

Anchor's PDA guide and seed constraints provide the implementation vocabulary. The engagement should still specify which relationships the seeds encode. A technically valid derivation does not prove that the account belongs to the action the caller requested.

Include closure and recreation in the lifecycle review. Decide whether an account's address can be reused and what must be reset before reuse. If another account stores a reference to the old state, test how that reference behaves after closure. A creation test alone cannot establish those later relationships.

CPI target validation

A CPI delegates work to another program while passing account privileges. Solana's CPI documentation states that a callee cannot escalate privileges beyond those passed by the caller. That runtime protection does not establish that the caller chose the intended callee or forwarded only the authority its design required.

Validation boundaries before a cross-program call Caller-supplied accounts pass through type and owner validation, then authority and PDA relationship checks, then callee and privilege selection. Only after those checks does the external program receive the chosen account privileges. Account validityType and ownerApplication authoritySigner and seedsDelegation boundaryCallee and privilegesRuntime privilege checks do not replace the application's choice of trusted callee.
Different checks answer different trust questions before the program delegates an operation.
Account validity
Establish that supplied data has the expected type and owner.
Application authority
Bind the authorized actor and derived state to the requested operation.
Delegation boundary
Select an allowed program and pass the minimum privileges the operation needs.

Test a valid executable account with the wrong program ID. Being executable is not equivalent to being the approved dependency. Where the design supports multiple programs through an interface, define the accepted set and its behavior, including how an upgrade to an accepted program affects your assumptions.

Include failure after the call. Recheck any state assumptions that depend on accounts changed through CPI, and follow the framework's refresh behavior for deserialized data. Review errors and compute exhaustion with the intended transaction atomicity. A dependency's failure must not be silently converted into a successful application result.

A token transfer hook adds another program to this dependency graph. The review should identify what the hook receives and whether its behavior can prevent an otherwise expected transfer. Keep the hook implementation in scope when the application's availability or accounting depends on it.

Token-2022 extension surface

Accepting the Token-2022 program ID establishes a program family, not compatibility with every mint configuration. The official extension guide describes features that affect received amounts, privileges and the circumstances in which transfers can execute. Decide which of those behaviors your application supports before describing a token integration as compatible.

Extension behavior translated into integration tests
FeatureChanged assumptionTest or explicit policy
Transfer feeThe credited amount may differ from the requested amountReconcile received balances and fee treatment
Permanent delegateAuthority can exist beyond the account holderAccept or reject the disclosed delegate capability
Transfer hookMovement invokes additional program logicReview hook accounts and failure behavior
CPI guardSome operations are restricted within a program callTest the application's actual invocation path
Scaled UI amountDisplayed units can differ from raw accounting unitsKeep pricing and balances in defined units

Inspect both the mint and the token accounts used by the transaction. A mint-level review can miss an account-level restriction. Conversely, a token account that worked in an earlier test does not prove that the mint's controlling authority cannot change an accepted behavior later.

Anchor exposes extension helpers and constraints, but helper availability is not an application acceptance policy. For each supported configuration, record the relevant dependency version and the expected result of a transfer through your program. Include a destination with different account-level settings.

Reject unsupported cases explicitly. If your accounting credits the requested transfer amount, test a fee-bearing mint before allowing it. If the application promises unrestricted exit, inspect whether a delegate, pause authority or hook can invalidate that promise. Our permissioned-token review explains why these controls must also match the rights described to holders.

What to hand an auditor

The useful handoff is a reproducible program package with a threat model and a deployment manifest. A repository link plus a deadline leaves the reviewer to discover which code, accounts and configurations the team considers authoritative. Resolve those choices before the engagement clock becomes the constraint.

  1. Pin the source revision and toolchain.
  2. Include dependency lockfiles, build instructions and the intended program ID. Record the target cluster and the feature assumptions used by tests.
  3. Provide the IDL, account layouts, seed schemas and authority map. Name every unchecked account and explain the validation expected for it.
  4. Supply a token policy that enumerates supported program IDs, mint extensions and account-level conditions. Attach fixtures that exercise both accepted and rejected configurations through the real instruction path.
  5. Deliver invariants and expected results for lifecycle transitions, including closure, recovery and upgrades where applicable. Include failed operations, not just successful examples.
  6. Agree on remediation verification against the release artifact. Preserve accepted risks with their owners and the conditions that would require another review.

Anchor's verifiable-build documentation explains why pinned build environments matter and how source-derived artifacts can be compared with deployed programs. A reproducible binary comparison establishes a link between artifacts. It does not establish that the program's behavior is safe.

Keep a small release checklist beside the package. Someone other than the author should be able to identify the target program, reproduce the build and locate the authority configuration without asking for missing context. Record any step that still depends on a private workstation or an undocumented credential.

State which deployment actions are included. A program upgrade may preserve the address while changing the code, and an authority change may alter who can perform the next upgrade. The report should identify the reviewed binary or source revision and the authority configuration inspected with it.

Use the fuzzing workflow to turn account substitutions and lifecycle transitions into repeatable tests. Keep the assertions specific enough that a failing run identifies the violated relationship instead of reporting only that a transaction reverted.

Shortlisting firms that read Anchor

Ask for evidence of relevant program work. A firm can have extensive Solidity experience while assigning a different team to a Rust engagement. Inspect a comparable report for account relationships, PDA validation and dependency scope, then confirm who will perform the proposed review.

Pharos Production describes smart contract security audits for Rust and Solana alongside manual code review, proof-of-concept findings and remediation verification. Use the stated deliverables to frame a request for comparable program work, then ask which account constraints and token extensions the proposed scope includes.

The member directory and a published firm analysis help locate evidence to inspect. They do not establish that every listed firm has reviewed your particular extension combination. Compare the report's scope with the acceptance policy you prepared, including external programs and administrative authority.

Evidence that makes Solana audit proposals comparable
Proposal questionUseful answerRemaining uncertainty
Who reads the program?Named reviewers with relevant public workExperience on the specific application model
What is included?Program, account and dependency manifestUnreviewed external behavior
How are fixes checked?Reproduction and remediation verification processChanges after the reviewed revision
What drives the quote?Explicit scope assumptions and review effortWork added when configuration or dependencies change

Solana audit cost cannot be inferred from instruction count alone. Compare what each proposal includes: custom account logic, dependency review and retesting can change the amount of work. Request a revised quote when the scope changes, and retain the assumptions that made the earlier quote meaningful.

Before accepting the deliverable, use our audit report walkthrough to check revision identifiers and finding statuses. The report should let another engineer connect a stated conclusion to the account relationship or execution path actually tested.

Frequently asked questions

Can the review use a closed-source external program?

The reviewer can examine your integration and documented assumptions, but cannot make a source-review claim about code they were not given. Identify the external program, the privileges passed to it and the uncertainty that remains. Agree whether that limit is acceptable for the intended release.

Should the scope include an off-chain transaction builder?

Include it when application correctness depends on how the client constructs account metadata or selects instructions. Supply representative transactions and the client revision so the reviewer can compare intended accounts with the program constraints. State explicitly when the engagement covers only the on-chain program.

What if the audit begins before the final mint configuration exists?

Provide representative fixtures and state the permitted configuration set. Reconcile the final mint and account settings with that set before release. Any unsupported extension or authority change needs a documented review decision rather than an assumption that the earlier test still applies.