Skip to content

ReceiptAnchor has no upgrade path, so any bug found after deployment is permanent #181

Description

@mallison031

Repo context. accensa-contracts holds the on-chain half of Accensa: ReceiptAnchor
(Merkle batch anchoring so an agent can verify it was charged correctly without trusting
the seller's API) and RefundVault (policy-bounded refunds without the merchant becoming
a custodian). Both are deployed on testnet. soroban-sdk 27.0.4, MIT. Read README.md,
docs/SECURITY_MODEL.md and DEPLOYMENTS.md before starting.

Problem

ReceiptAnchor exposes initialize, anchor_batch, get_batch, verify_receipt,
get_batch_count, extend_batch_ttl and prune_batches. There is no upgrade, and
soroban_sdk's env.deployer().update_current_contract_wasm() is never called.

That means the deployed WASM is final. Every correctness issue already filed against this
contract — the unbounded proof loop (#96), the missing domain separation (#147), the
prune_batches budget exhaustion (#149) — is unfixable on the live testnet deployment. The only
remedy is to deploy a new contract at a new address and ask every integrator to re-point, which
also orphans every batch already anchored: verify_receipt on the old address keeps working,
but the new one starts at batch 1 with no history.

For a contract whose value proposition is long-lived auditability, "we redeploy and lose the
history" is not an acceptable recovery story.

What to do

This needs a design decision before code. Immutability is a legitimate choice for an anchoring
contract — it is arguably why an auditor should trust it — so the point of this issue is to
make the choice deliberately and write it down.

  • Write an ADR (follow docs/ADR-001-merkle-structure.md for format) arguing for either a
    permanent admin-gated upgrade, a time-locked upgrade, an upgrade that can be permanently
    renounced, or deliberate immutability.
  • If the ADR lands on upgradeable, implement it: admin-gated upgrade(new_wasm_hash: BytesN<32>)
    with require_auth, an UpgradeEvent, and a test that exercises an actual upgrade through
    env.deployer().
  • If the ADR lands on immutable, say so prominently in README.md and docs/SECURITY_MODEL.md,
    and state what the migration story is instead.

Note on scope

#126 covers RefundVault state migration. This issue is ReceiptAnchor and should reach a
consistent answer with it — coordinate before starting.

Acceptance criteria

  • An ADR in docs/ states the decision and its reasoning.
  • The code matches the ADR, with tests.
  • README.md tells an integrator which guarantee they are getting.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions