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.

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.
| Review dimension | Evidence to request | Failure to test |
|---|---|---|
| Account identity | Expected addresses and derivation rules | A validly typed substitute account |
| Authority relationship | Stored authority and required signer condition | An unrelated signer authorizing another user's action |
| Program dependency | Allowed callee IDs and forwarded privileges | An unintended program receiving signing authority |
| Token behavior | Accepted mint and account extensions | A 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
ExtensionTypeenum and its account-type mapping ininterface/src/extension/mod.rsof 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
Uninitializedpadding 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
| Observation | Count | Denominator | Audit use |
|---|---|---|---|
| Mint extension types | 19 | 28 included types | Inspect mint-level behavior and authorities |
| Token-account extension types | 9 | 28 included types | Inspect the actual source and destination accounts |
| Mint types requiring account extensions | 4 | 19 mint types | Check initialization dependencies |
| Unclassified types | 0 | 28 included types | The 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.
- List each PDA's purpose and seed schema.
- 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.
- 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.
- 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.
- 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.
| Feature | Changed assumption | Test or explicit policy |
|---|---|---|
| Transfer fee | The credited amount may differ from the requested amount | Reconcile received balances and fee treatment |
| Permanent delegate | Authority can exist beyond the account holder | Accept or reject the disclosed delegate capability |
| Transfer hook | Movement invokes additional program logic | Review hook accounts and failure behavior |
| CPI guard | Some operations are restricted within a program call | Test the application's actual invocation path |
| Scaled UI amount | Displayed units can differ from raw accounting units | Keep 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.
- Pin the source revision and toolchain.
- Include dependency lockfiles, build instructions and the intended program ID. Record the target cluster and the feature assumptions used by tests.
- Provide the IDL, account layouts, seed schemas and authority map. Name every unchecked account and explain the validation expected for it.
- 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.
- Deliver invariants and expected results for lifecycle transitions, including closure, recovery and upgrades where applicable. Include failed operations, not just successful examples.
- 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.
| Proposal question | Useful answer | Remaining uncertainty |
|---|---|---|
| Who reads the program? | Named reviewers with relevant public work | Experience on the specific application model |
| What is included? | Program, account and dependency manifest | Unreviewed external behavior |
| How are fixes checked? | Reproduction and remediation verification process | Changes after the reviewed revision |
| What drives the quote? | Explicit scope assumptions and review effort | Work 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.