Skip to content

Make the docs match the programs, and close the gaps nothing was checking - #131

Merged
mikemaccana merged 6 commits into
mainfrom
claude/docs-match-implementation
Aug 20, 2026
Merged

Make the docs match the programs, and close the gaps nothing was checking#131
mikemaccana merged 6 commits into
mainfrom
claude/docs-match-implementation

Conversation

@mikemaccana

Copy link
Copy Markdown
Collaborator

The follow-up work from the repository audit: documentation that described programs as they used to be, two examples that did not do the thing their README said they did, and three places where CI was not looking.

Six commits, each independent. Merges cleanly onto main as of e0fcaa25.

ci: fix the two red workflows on main

The ASM workflow installs the sbpf assembler with cargo install --git. Upstream now ships a second binary crate, xtask, and cargo refuses to pick between two binaries, so the install step dies before any project is built. Naming the package fixes it. This is still red on main.

This commit also reformats basics/cross-program-invocation/anchor/idls/lever.json, which #130 has since landed independently. Same content, so the merge is a no-op for that file.

transfer-hook: keep the transfer count the examples say they keep

Both counter examples exist to show a hook writing to a PDA, and both READMEs say the hook increments a counter on every transfer. It did not. The handler added one to the stored count, logged the result and dropped it: counter_account was not mut, so nothing was written back and the count read one after every transfer no matter how many had happened. The extra account meta already declares the PDA writable, so the fix is the mut constraint and the assignment. A new assertion in each test reads the counter back after the hooked transfer.

The dead amount > 50 check kept a commented-out return err!(...) beside it. amount arrives in minor units, so any transfer of a token with decimals clears 50 and returning there would fail the example's own test. Replaced the commented-out line with a comment saying what to change to make the limit binding. AmountTooBig was also standing in as the checked-add overflow error, which is a different failure, so CounterOverflow now covers that.

docs: describe the programs as they are now implemented

Five READMEs still showed pre-port code, so the repository documented account types, handler signatures and CPI builders that no longer exist.

  • tokens/nft-operations showed CreateMetadataAccountV3Cpi::new(...).invoke_signed() where the program uses anchor-spl's create_metadata_accounts_v3 with a CpiContext, and VerifyCollectionV1Cpi where the source calls verify_sized_collection_item
  • transfer-hook/account-data-as-seed: free function for the extra metas, AccountConstraints suffixes, UncheckedAccount for the meta list
  • finance/order-book: initialize_market takes base_lot_size and quote_lot_size with the errors that reject zero, place_order takes &mut Context, state stores Address, maker pairs arrive as AccountViews from context.remaining_accounts()
  • basics/close-account and finance/token-fundraiser: BorshAccount, #[account(borsh)], Address, user.address() in seeds
  • basics/counter and finance/token-fundraiser named an initialize handler; they are initialize_counter and initialize_fundraiser

Every fenced Rust block in every tracked markdown file now matches its source, and every identifier named in prose resolves to a real definition.

comments: stop describing the framework as Anchor 1.0

Twenty comments across workflows, manifests and program sources explained a version skew or a workaround in terms of "Anchor 1.0" or "anchor-lang 1.0". A reader checking one against the manifest finds a version that is not there and cannot tell whether the workaround still applies. Two mentions stay, both true statements about the past.

kani: lint the proof crates, which nothing was linting

Each proof crate declares its own [workspace], deliberately, so the Kani model does not drag in the Solana dependency tree. That also means the repository-wide cargo fmt and cargo clippy jobs never see it, and nothing else did either: seven of the eight crates had drifted out of rustfmt. kani.yml already runs a per-crate matrix, so the fmt and clippy steps go there.

token-swap's copy of integer_sqrt needed two fixes to pass clippy. It is documented as a verbatim copy of the program's function, so the (x + 1) / 2 clippy wants as div_ceil changed in the program and the proof together, keeping them identical.

ci: typecheck and verify the vault strategy app

finance/vault-strategy/anchor/app is the only TypeScript application here: 110 files, an Anchor client, a committed IDL. Biome formatted and linted it; nothing compiled it, so a client that had drifted from the program's IDL looked fine. The app already ships the two scripts that catch it, and both pass today.

Verification

cargo fmt --all --check and cargo clippy -- -D warnings -A clippy::diverging_sub_expression clean at the repository root; all eight kani-proof crates clean on fmt, clippy and their unit tests; biome check over the tracked files CI sees reports zero errors.

Not verified: the transfer-hook counter change has never been executed. anchor test needs a toolchain this session cannot install, so it is backed by cargo check and by the extra account meta already declaring the PDA writable. CI on this PR is the first real run.

This does not touch the nine projects failing at anchor idl build; those are tracked in the discussion on #130.


Generated by Claude Code

claude added 6 commits August 20, 2026 01:55
The TypeScript workflow runs `biome check ./`, which formats JSON as well as
TypeScript. `basics/cross-program-invocation/anchor/idls/lever.json` was
regenerated by `anchor build` with one array element per line, which Biome
reformats onto a single line, so the check has failed on every commit since
the IDL was last regenerated. Reformatted; the file's contents are unchanged.

The ASM workflow installs the sbpf assembler with `cargo install --git`. The
upstream repository now ships a second binary crate, `xtask`, and cargo
refuses to pick between two binaries, so the install step dies before any
project is built. Naming the package fixes it.
Both counter examples exist to show a hook writing to a PDA, and both READMEs
say the hook increments a counter on every transfer. The handler added one to
the stored count, logged the result, and dropped it: `counter_account` was not
`mut`, so nothing was ever written back and the count read one after every
transfer no matter how many had happened. The extra account meta already
declares the PDA writable, so the fix is the `mut` constraint and the
assignment. A new assertion in each test reads the counter back after the
hooked transfer.

The dead `amount > 50` check kept a commented-out `return err!(...)` beside
it. `amount` arrives in minor units, so any transfer of a token with decimals
clears 50, and returning the error there would fail the example's own test.
Replaced the commented-out line with a comment saying what to change to make
the limit binding.

`AmountTooBig` was also standing in as the checked-add overflow error, which
is a different failure. Added `CounterOverflow` for that.
The v2 port changed code that five READMEs still show in its pre-port form,
so the repository documented account types, handler signatures and CPI
builders that no longer exist. Each code block now matches the source it is
quoting, and each identifier named in prose resolves to a real definition.

- `tokens/nft-operations`: the CPIs go through anchor-spl's
  `create_metadata_accounts_v3` / `create_master_edition_v3` /
  `verify_sized_collection_item` wrappers and a `CpiContext`, not the
  `mpl-token-metadata` `*Cpi` builders with `invoke_signed`. Added a note on
  why, and on how the read-only and mutable `CpiHandle`s differ.
- `tokens/token-extensions/transfer-hook/account-data-as-seed`: the extra
  account metas come from a free function, the constraint structs carry the
  `AccountConstraints` suffix, and `extra_account_meta_list` is an
  `UncheckedAccount`.
- `finance/order-book`: `initialize_market` takes `base_lot_size` and
  `quote_lot_size` (with the errors that reject zero), `place_order` takes
  `&mut Context`, the state accounts store `Address`, and the maker pairs
  arrive as `AccountView`s from `context.remaining_accounts()`.
- `basics/close-account` and `finance/token-fundraiser`: `BorshAccount`,
  `#[account(borsh)]`, `Address`, and `user.address()` in seeds.
- `basics/counter` and `finance/token-fundraiser` named an `initialize`
  handler; the handlers are `initialize_counter` and
  `initialize_fundraiser`.

`tokens/nft-operations`'s program is not a workspace member, so nothing lints
it: its account fields were private (which the README's example could not be),
one handler took a `mut` binding on a reference, and one `use` was not
rustfmt-clean. Fixed alongside the README that quotes them.
Twenty comments across workflows, manifests and program sources explained a
version skew or a workaround in terms of "Anchor 1.0" or "anchor-lang 1.0".
The programs are on 2.0.0-rc.1, so a reader checking one of these against
the manifest finds a version that is not there and cannot tell whether the
workaround still applies. Each now names v2.

Two mentions stay: `README.md` on when `anchor init` began scaffolding
LiteSVM, and the abl-token manifest on when `interface-instructions` was
removed. Both are statements about the past and both are true.
Each proof crate declares its own `[workspace]` (deliberately: the Kani model
must not drag in the Solana and SPL dependency tree), so the repository-wide
`cargo fmt` and `cargo clippy` jobs in rust.yml never see it. Nothing else
did either, and seven of the eight crates had drifted out of rustfmt.

`kani.yml` already runs a per-crate matrix for the unit tests, so the fmt and
clippy steps go there. Reformatted all eight.

`token-swap`'s copy of `integer_sqrt` also needed two fixes to pass clippy.
It is documented as a verbatim copy of the program's function, so the
`(x + 1) / 2` that clippy wants written as `div_ceil` is changed in the
program and in the proof together, keeping the two identical. Only the
`#[cfg(kani)]` proof and the unit tests call it, so the plain library build
sees no caller; that now says so with an `allow`.
`finance/vault-strategy/anchor/app` is the repository's only TypeScript
application: 110 files, an Anchor client, and a committed IDL. The TypeScript
workflow ran Biome over it, which formats and lints but never compiles, so a
client that had drifted from the program's IDL would have looked fine.

The app already ships the two scripts that catch it. `typecheck` compiles it,
and `verify` exercises instruction encoding, account decoding and PDA
derivation against the committed IDL offline, with no validator. Both pass
today; CI now runs them.
@mikemaccana
mikemaccana merged commit 6f3c5ca into main Aug 20, 2026
33 of 40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants