On-chain program upgrade analysis

SolDiff

Understand what changed.

Reconstruct historical Solana program versions from upgrade buffer writes, then compare the binaries with evidence-backed diffs — not marketing claims.

Illustrative reconstruction flow — live reports use real on-chain signatures and hashes.

01 / Mainnet pairs

Run a real mainnet upgrade diff.

Five verified on-chain upgrade pairs (Token-2022 → Kamino stress → reused buffer). Open Analyze with the exact program ID and upgrade signatures — reports come from the live pipeline, not hand-written fixtures.

Case A

Token-2022

Lightest real pair (~1.35 MB). Distinct buffers: GcAbnM4D… (542 writes) → BsRtj52F… (629 writes). Both cycles verified clean. Non-Anchor — exercises historical IDL unavailable (never silently unchanged).

Program ID
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Version A · slot 346,170,941
39CiabTipsmCxWLVsY9ciVBuY5FNHUr2FpF5x6shs42w6KFjx1J2nogMtJezwgNfyAxDgTexZwc3P1SEUMd2vhPP
Version B · slot 427,147,035
2cM3S25AJnHyy4shW7zsoqz5W8JPXPvXiUxk545n5ANf6BET9VvBRfsnSNYi9MqogjVWNBxNfaZpE9QBJX4XCbfn
02 / Reconstruction

From upgrade signatures to
evidence-backed bytecode diffs.

Built around trustworthy historical reconstruction first. Advanced semantic claims stay out of scope until they can be proven.

01

Parse upgrade transactions

SolDiff reads two BPF Upgradeable Loader Upgrade transactions, extracts buffer / Program / ProgramData addresses, and validates Version A is older than Version B.

parseUpgradeTransaction(sigA|sigB)
02

Reconstruct historical ELFs

Buffer Write instructions are collected, ordered deterministically, isolated to a deployment cycle, coverage-checked, and assembled into validated ELF64 artifacts with full SHA-256 identity.

Write replay → coverage map → validateElf()
03

Raw byte + SBF instruction diff

`.text` and `.rodata` are compared at the byte-chunk layer. An SBF instruction-level decoder adds sequence-aligned instruction evidence. This is not a semantic decompiler.

raw-byte diff + soldiff-ebpf-isa / optional sbpf|llvm-objdump
04

Optional Anchor IDL diff

When an IDL can be historically matched, instruction/account/discriminator changes are reported with evidence. Otherwise historical IDL is marked unavailable — never silently unchanged.

normalizeIdl → compareNormalizedIdls
05

Evidence-backed findings

Heuristic findings attach analyzer, confidence, and evidence. Overstated claims (e.g. treating sampled pubkeys as CPI targets) are avoided.

Finding{ analyzer, code, severity, confidence, evidence }
06

Reproducible case study

CLI/scripts emit manifest.json + report.md with hashes and provenance. Reports are ephemeral in the UI today — persistence/shareable URLs are not shipped yet.

bun run case-study --program … --from … --to …
03 / Rule catalog

Ten rules that catch
real exploits.

Every rule is derived from a real Solana incident. The engine targets a zero false-negative rate on CRITICAL findings — if it fires, treat the upgrade as dangerous until proven otherwise.

REMOVED_SIGNER_CHECKCRITICAL

A required signer constraint was removed from an instruction. Any caller can now invoke it without authorization.

e.g. liquidate_perp: #[account(signer)] → #[account]
NEW_INVOKE_SIGNED_TARGETCRITICAL

A new CPI target was added inside invoke_signed. A program PDA authority is now used to CPI to an unverified program.

e.g. place_order: new CPI → xYz3...kP9m
CHANGED_AUTHORITY_FIELDCRITICAL

An account marked as authority or admin has changed its ownership or address constraint.

e.g. config.upgrade_authority: old_key → new_key
DISCRIMINATOR_CHANGEHIGH

An instruction's 8-byte discriminator was modified — a breaking change that can silently break all existing clients.

e.g. withdraw: discriminator [0x4a..] → [0x9f..]
NEW_MUTABLE_ACCOUNTHIGH

An account that was previously read-only is now marked as writable. Enables unexpected balance mutations.

e.g. settle_pnl: insurance_fund_vault → #[account(mut)]
REMOVED_OWNER_CHECKHIGH

An owner validation (constraint on account.owner) was removed, enabling account substitution attacks.

e.g. deposit: owner = token_program removed
ADDED_CLOSE_ACCOUNTMEDIUM

A new close_account CPI was added. Could be used to drain PDA lamports to an attacker-controlled destination.

e.g. admin_withdraw: new close_account(escrow)
CHANGED_SEEDSMEDIUM

PDA derivation seeds changed. The PDA address changes — existing records become inaccessible, potential account substitution.

e.g. deposit: seeds ["v1", user] → ["v2", user]
NEW_32_BYTE_PUBLIC_KEY_CANDIDATELOW

Live pipeline: a sampled 32-byte window decodes as a public key. Hypothesis only — not a proven CPI target.

e.g. sampled bytes → candidate pubkey (confidence=low)
LARGE_TEXT_REGION_CHANGEDINFO

Live pipeline: many aligned .text chunks differ. Measures raw churn, not proven semantic logic change.

e.g. .text: 48 chunks differ
Community rule contributions
New rule patterns can be submitted as a PR. Rules are versioned, tested, and reviewed before merge.
04 / Workflow

Fits into your existing workflow.

Web UI, CLI, TypeScript SDK, or GitHub Actions. One engine, every entry point.

CLInpx soldiff

Run in any terminal. Diff by slot, upgrade transaction, or program ID. Outputs JSON for CI pipelines.

$ npx soldiff JUP4...GuJB \
    --from-slot 280000000 \
    --to-slot 284500000 \
    --output report.html
Squads v4 SDK@soldiff/sdk

Generate a diff report before submitting an upgrade transaction. Attach the shareable URL to the multisig proposal.

import { generateDiffReport } from "@soldiff/sdk";

const report = await generateDiffReport({
  programId: new PublicKey("JUP4...GuJB"),
  fromSlot: 280_000_000,
  toSlot: 284_500_000,
});
console.log(report.shareableUrl);
GitHub Actionssoldiff-action

Monitor programs automatically. Get Slack or webhook alerts the moment any watched program is upgraded.

- uses: MisbahAnsar/soldiff-action@v1
  with:
    program-ids: |
      JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB
    rpc-url: ${{ secrets.HELIUS_RPC_URL }}
    alert-on-severity: HIGH
    notify-slack: ${{ secrets.SLACK_WEBHOOK }}
Governance ready

Immutable, content-addressed reports.

SolDiff reports are hashed by SHA-256 and permanently hosted. Multisig members and DAO voters can attach a SolDiff URL to any upgrade proposal on Realms or Squads — if the URL still loads, it is the same report that was reviewed.

Realms / SPL GovernanceSquads v4Serum MultisigMean Finance DAO
SHA-256
Content-addressed