← Solana Builder

Intermediate · LESSON 05 OF 06

5. Anchor validation and negative tests

By the end, you can translate invariants into constraints and executable checks.

In this lesson7 field notes · practice · sources · checkpoint
Negative tests vary issuer authority, address derivation, duplicate claims, level, and revision to check that invalid inputs are rejected.
Negative tests vary issuer authority, address derivation, duplicate claims, level, and revision to check that invalid inputs are rejected.Open full-size visual ↗
Read a text version of this diagram

The test suite changes one security-relevant input at a time: issuer authority, supplied address, duplicate claim, level, or course revision. It expects invalid cases to fail at the proper transaction, account-validation, or handler boundary, then checks that protected state did not change. A successful control confirms that legitimate claims still work.

  1. ISSUER AUTHORIZATION
  2. WRONG AUTHORITY
  3. CANONICAL ADDRESS
  4. DUPLICATE CLAIM
  5. LEVEL / REVISION
A security test records protected account state, changes one attacker-controlled input, runs the case at the correct transaction or program boundary, checks the expected rejection, and reloads state to verify it stayed unchanged. A successful control proves the intended path still works.
Test the exact boundary that should reject the attack, then prove protected state stayed unchanged.Open full-size visual ↗
FIELD NOTE 01

Types are a starting point

Anchor account types and constraints cover different checks. `Signer` or a signer constraint checks that the account signed the transaction; it does not prove that this signer is the authority stored in another account. `Account<T>` checks the owner program and deserializes the expected type. `has_one` can compare a stored public key with the supplied account, while `seeds` and `bump` validate a PDA relation. Combine the checks required by the invariant; no one type or annotation proves every relationship. Anchor performs its account-context checks before the handler runs, so identify that boundary in the tests and read the behavior for the exact framework version you use.

FIELD NOTE 02

Test the attacker’s inputs

For a state-changing instruction, test a valid-but-unauthorized signer, wrong owner program, wrong stored authority, wrong mint, wrong PDA, duplicate invocation, and boundary values. A missing required transaction signature is rejected before program logic executes; test that transaction-level gate separately from an instruction that executes and fails an Anchor account constraint. For each failure, assert the expected boundary and preserve relevant account data and token balances. An error string alone does not prove that protected state stayed unchanged.

FIELD NOTE 03

Build a local feedback loop

Pin tool versions and use a local test environment. Keep fixtures separate from live keys. Reproduce a successful instruction first, then mutate one input at a time. Record which invariant each test protects so future refactoring does not turn a useful test into a snapshot of incidental implementation details.

FIELD NOTE 04

Negative tests define the security boundary

A passing happy-path test shows only that one expected sequence works. For every instruction, write who may call it, which account keys and relationships must match, which fields can change, and what must remain unchanged on failure. Test a wrong signer who did sign, wrong PDA seeds, wrong mint, stale state, duplicate invocation, and unexpected writable inputs. A transaction missing a required signature is rejected before the program handler; do not describe it as a handler-level rejection. Capture the expected error at its actual boundary and compare protected state before and after the attempt. Add a successful control case so the security change does not block the intended caller.

FIELD NOTE 05

Choose the test boundary deliberately

A helper unit test, an in-process Solana program harness, a local validator, and a mocked RPC client observe different behavior. LiteSVM runs a fast in-process Solana VM and can execute compiled program bytes, but it is not a full RPC node. A local validator adds validator and RPC behavior for flows that depend on account creation, transaction handling, or RPC methods. Mocks are useful for delayed, null, malformed, and stale client responses; they do not prove the program enforces an invariant. Choose the harness that observes the claimed failure boundary, and record its versions and limits.

FIELD NOTE 06

Turn each invariant into an adversarial case

For every rule, name the expected failure boundary. If only the learner may update a record, change the signer. If only a particular mint is allowed, pass a different valid mint. If a PDA must be canonical, change a seed or bump. If an exam claim is one time, replay it. In every failure test, assert both the error and the postcondition that protected account state did not change. Also test a successful instruction so a security fix cannot silently make legitimate learners unable to complete the flow.

FIELD NOTE 07

Use properties and fuzzing to find input gaps

Example-based tests cover cases you remember to write. Property tests can vary addresses, amounts, or instruction sequences against a stated rule such as “failed authorization never changes protected state.” Coverage-guided fuzzing can explore action sequences and check invariants after each action; it can reveal cases missing from the hand-written matrix, but a clean run does not prove all inputs safe. When a run finds a failure, preserve and minimize the reproducing sequence, then add it as a regression test. Anchor’s current testing references describe LiteSVM, Mollusk, and fuzzing; choose the tool supported by your pinned framework and host environment.

MAKE THE CONNECTION

A detail worth keeping.

Positive tests establish that a supported caller can make progress. Negative tests establish the security boundary. For an unauthorized attempt, inspect account bytes, balances, and mint count before and after, not just the error message. Include property-based variations for addresses and amounts where available.

Work through an example

Use a test validator with disposable fixtures and pinned program/tool versions. Document the exact command and expected result. Never point automated tests at mainnet signing credentials. A green test suite is evidence for those test cases, not a protocol audit.

Write a test matrix for a credential instruction with six hostile inputs. For each, state the expected rejection and the account state that must remain unchanged.

GUIDED PRACTICE · INTERMEDIATE

Do the work, one step at a time.

Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Most exercises work on paper or with a local test; only a lesson that clearly says “read-only Devnet” makes a public network request.

  1. Download Anchor credential lab 01 and unzip it outside the course repository.
  2. Follow its README to verify Anchor 1.2.0, the Solana CLI, and the pinned LiteSVM test toolchain in WSL Ubuntu.
  3. Run anchor build and identify the compiled SBF program artifact.
  4. Run anchor test and inspect the four LiteSVM cases for issuer authorization, canonical addresses, duplicate claims, levels, and revisions.
  5. Explain why this teaching registry is not an NFT and how the production passport instead uses Metaplex Core.

Before you move on, check your reasoning

  • Write a seven case threat-driven test matrix.
  • Make each negative case assert unchanged state.
  • Run a clean local test from a fresh checkout and record versions.

OPEN SELF-CHECK · UNGRADED

Pause and test your reasoning

An Anchor test passes for one expected signer and one valid PDA. What does that establish?

  1. Every signer, PDA, deployment, RPC endpoint, and production account layout is safe.
  2. The assertions passed for the tested setup and cases; additional negative, boundary, and deployment checks remain necessary.
  3. The program cannot be vulnerable because Anchor generated the account context.
Reveal one best answer and its explanation

One best answer: The assertions passed for the tested setup and cases; additional negative, boundary, and deployment checks remain necessary.

A test is evidence about the cases and assertions it actually exercised. Add wrong-signer, wrong-owner, wrong-PDA, replay, boundary, and state-invariant failures; local test success is not proof of deployment or RPC health.

Sources: Anchor Testing · Fuzzing

This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.

Never enter a recovery phrase or private key into a learning site or lesson exercise.

DOWNLOADABLE PROGRAM LAB · LOCAL TESTING

Test issuer checks and one-time course claims.

This Anchor program is a teaching exercise that records a wallet-bound course result and curriculum revision. It is not the academy’s NFT issuer and does not mint a Metaplex Core asset. Its LiteSVM tests execute a compiled SBF program locally, without a validator, wallet file, RPC endpoint, or real SOL.

Download Anchor credential lab 01

Requires WSL Ubuntu, Rust, Solana CLI, and Anchor 1.2.0. Follow the included README, then run anchor build and anchor test. Never add learner names, email addresses, scores, or answers to public account data.

READ THE SOURCE

Go deeper in the official docs

Reviewed on 2026-09-29. Protocol documentation can change; check the linked page’s current version as you use this material.

  1. Account Constraints ↗
  2. Account Types ↗
  3. Anchor Testing ↗
  4. Anchor Installation ↗
  5. LiteSVM ↗
  6. Anchor.toml Configuration ↗
  7. Transaction Pipeline ↗
  8. Fuzzing ↗

Report an issue with this lesson

OPEN LEARNING

Keep learning at your own pace.

This lesson, its guided practice, and all six course lessons are open to everyone. Account-based checkpoints, exams, and saved progress will open when free accounts are ready.

Continue to the next lesson ↗