Skip to content

Solution: LP-0013 — Token program authorities#62

Draft
ego-errante wants to merge 3 commits into
logos-co:masterfrom
ego-errante:lp-0013-solution
Draft

Solution: LP-0013 — Token program authorities#62
ego-errante wants to merge 3 commits into
logos-co:masterfrom
ego-errante:lp-0013-solution

Conversation

@ego-errante
Copy link
Copy Markdown

@ego-errante ego-errante commented May 23, 2026

Solution: LP-0013 — Token program authorities

Submitted by: ego-errante

Summary

This solution adds a rotatable mint authority model to the LEZ Token program. The implementation is built around lez-approval — a new agnostic single-admin approval crate that fulfils RFP-001 — and exposes three new instructions (NewFungibleDefinitionWithAuthority, MintWithAuthority, RotateAuthority, RevokeAuthority) layered additively over the existing Token surface. Revocation is terminal (Authority::renounced() is a permanent sentinel), so fixed-supply tokens are expressible as "mint everything, then revoke."

Repository

  • Repo: https://github.com/ego-errante/logos-execution-zone
  • Branch: lp-0013-token-authorities
  • Commit: c9be6e86
  • Key files:
    • lez-approval/src/lib.rs — RFP-001 agnostic approval library (Authority, ApprovalError)
    • programs/token/core/src/lib.rsInstruction enum extensions and the canonical Authority field on TokenDefinition::Fungible
    • programs/token/src/{new_definition,mint,rotate}.rs — handler implementations
    • program_methods/guest/src/bin/token.rs — guest dispatcher
    • wallet/src/{program_facades,cli/programs}/token.rs — wallet facade + CLI subcommands
    • examples/{fixed-supply,variable-supply}/ — two example integrations
    • integration_tests/tests/token_authority.rs — end-to-end lifecycle tests
    • artifacts/token.idl.spel.json — SPEL-emitted IDL (provenance, generated from spel-sidecar/)
    • artifacts/token.idl.json — hand-authored canonical IDL (completeness, conforms to SpelIdl)
    • spel-sidecar/ — sidecar Cargo package outside the workspace that hosts the SPEL-shape mirror
    • docs/SPEL_STATUS.md — disclosure of the IDL story (why a sidecar, the scaffold↔real mapping)
    • demo.sh — clean-checkout end-to-end demo (rotate / revoke flow ends with balance 1500)

Approach

Architecture

The Token program already had Mint, Transfer, NewFungibleDefinition, etc. as variants of a single Instruction enum dispatched by program_methods/guest/src/bin/token.rs. We layer the authority model additively: three new instruction variants (NewFungibleDefinitionWithAuthority, MintWithAuthority, RotateAuthority, RevokeAuthority) coexist with the existing surface. The pre-existing Mint and NewFungibleDefinition paths are untouched, so naïve callers continue to work and the new variants are entirely opt-in for callers that want the gated lifecycle.

State-wise, the authority is recorded as a single new authority: Authority field on TokenDefinition::Fungible. We chose to store the authority on the definition rather than in a separate PDA (the Solana SPL pattern) because LEZ's per-account model already gives us atomic read-modify-write semantics within a single transaction — there is no concurrency benefit to splitting the field out, and a separate PDA would double the account count of every authority-gated instruction. The trade-off is that the authority field is a breaking Borsh-layout change for pre-existing TokenDefinition::Fungible accounts; this is documented in the TokenDefinition doc comment with the deferred FungibleV2 / separate-PDA alternatives that would preserve in-flight schema compatibility if LEZ ever introduces such a guarantee.

lez-approval as the RFP-001 deliverable

The agnostic approval library is implemented as a new top-level workspace crate, lez-approval. It exposes:

  • Authority(Option<AccountId>) — a single-admin wrapper whose None state is terminal (the renounced sentinel). The Option is intentionally hidden behind constructor (Authority::new, Authority::renounced) and predicate (Authority::is_renounced) methods so callers cannot accidentally reach into the None state via pattern matching and revive a renounced authority.
  • ApprovalError { Unauthorized, Renounced } — the two-variant error type that the panic-on-failure semantics surface as panic payloads. Unauthorized covers both "the wrong signer" and "the signer is not authorized at all"; Renounced covers all post-revocation rejections.
  • A gate / rotate / revoke contract that the Token program calls through.

LEZ guest programs panic on failure (the prover catches the panic and rejects the transaction), so we follow the same convention: every authorization failure panics with an ApprovalError payload rather than returning a Result. This matches the surrounding code and avoids each handler having to write match … panic!() boilerplate. The library is generic — it depends only on nssa_core::account::AccountId and could be reused verbatim by any other LEZ program that wants single-admin gating (e.g. a freeze authority on the same Token program, or a config authority on a future on-chain module).

Why the Logos stack

The single-admin authority model only earns its keep when the underlying execution layer makes it cheaply enforceable, atomic, and observably correct.

  • Trustless execution. Because every state transition is proved in a RISC0 guest, a renounced authority is provably renounced — there is no off-chain admin key that someone could secretly retain to revive it. On a centralised alternative the "revoked" state would always be a social commitment; on LEZ it is a circuit-enforced invariant.
  • Censorship resistance. Authority rotation is just another transaction in the LEZ mempool; the sequencer cannot selectively reject it without dropping the whole block. A centralised token-issuance API could refuse rotation requests from blocked accounts.
  • Atomicity at the right grain. The post-state of rotate_authority either updates the definition account's authority field or panics; there is no intermediate state where the authority is None but the new admin is not yet recorded. LEZ's per-account read-modify-write semantics give us this for free, which is why we did not need a two-phase commit pattern.

A centralised token-issuance backend could replicate the API surface, but it could not give a wallet a proof that a token's mint authority has been provably revoked. That is the property the Logos stack uniquely enables for this use case.

Alternatives considered

  • Separate authority PDA (Solana SPL pattern). Rejected for cost (doubles per-transaction account count) and complexity (no concurrency benefit on LEZ). Documented as a future option in TokenDefinition's doc comment if LEZ ever introduces in-flight Borsh-schema compatibility guarantees.
  • Result-returning handlers instead of panic-on-failure. Rejected for consistency with the surrounding LEZ guest convention — every other handler in the Token program panics on failure. Mixing return styles would be a maintenance trap.
  • In-place mutation of Instruction::Mint to add an optional authority argument. Rejected as a breaking change to the wire format that would have invalidated every existing caller. Additive variants are the correct LEZ idiom for this kind of evolution.
  • #[lez_program] directly on program_methods/guest/src/bin/token.rs to get a real-program SPEL IDL. Attempted and abandoned — pulling spel-framework into the workspace causes a hard nssa_core v0.1.0 vs v0.2.0-rc3 dep-graph collision (#[lez_program] proc-macro emits path-literal references to whichever nssa_core cargo picked for the macro-invoking crate; one universe per macro invocation). The [patch]-to-local-path workaround is a coin flip due to feature-flag and API drift. Full disclosure in docs/SPEL_STATUS.md. We instead ship the IDL via a sidecar scaffold (spel-sidecar/) that runs SPEL out-of-workspace, plus a hand-authored canonical IDL — matching the bar set by the prior community submission (PR #57).

Success Criteria Checklist

Functionality

  • Variable-size Tokens through minting authorityNewFungibleDefinitionWithAuthority sets the initial authority at definition creation; MintWithAuthority mints gated by the recorded authority; RotateAuthority and RevokeAuthority cover rotation and terminal revocation (renounced state is a permanent sentinel via Authority::renounced()).
  • Two example integrationsexamples/fixed-supply/ (mint everything, then revoke) and examples/variable-supply/ (rotatable inflation), both runnable end-to-end.
  • Self-sufficient, agnostic library per RFP-001lez-approval/ ships Authority, ApprovalError, and the gate / rotate / revoke primitives. The crate depends only on nssa_core::account::AccountId and is reusable by any LEZ program that wants single-admin gating.

Usability

  • Module/SDK for Logos moduleswallet/src/program_facades/token.rs exposes typed builders for the three new instructions; wallet/src/cli/programs/token.rs wires them as CLI subcommands. demo.sh exercises both surfaces end-to-end.
  • IDL for the updated Token program — Two artifacts: artifacts/token.idl.spel.json (SPEL-emitted via spel generate-idl against the spel-sidecar/ sidecar — provenance) and artifacts/token.idl.json (hand-authored against the SpelIdl schema — completeness, including the errors table for ApprovalError::{Unauthorized, Renounced} which the v0.4.0 CLI does not yet emit). See docs/SPEL_STATUS.md for the workaround rationale and reproducibility steps.

Reliability

  • Atomic rotation/revocation — Both handlers either return updated post-states or panic. The Authority state machine has no intermediate state: rotate is a single field write to authority: Authority::new(new_admin), and revoke is a single field write to authority: Authority::renounced(). Covered by integration tests rotate_then_mint_succeeds and revoke_after_authority_set_succeeds (both in integration_tests/tests/token_authority.rs).
  • Deterministic revoked-mint rejectionMintWithAuthority against a renounced definition panics with ApprovalError::Renounced, a documented error variant in lez-approval/src/lib.rs. Covered by integration test mint_after_revoke_panics. The panic-on-failure path is the canonical LEZ guest rejection mechanism.

Performance

  • Compute unit (CU) cost documentation — Pending Day 5. LEZ surfaces zkVM cycle counts at proof time; we will document the cycle delta for each new instruction (MintWithAuthority, RotateAuthority, RevokeAuthority) relative to the baseline Mint instruction in the README before flipping this PR to Ready.

Supportability

  • Deployed and tested on standalone sequencerdemo.sh runs the full lifecycle against a docker-compose-spun standalone LEZ sequencer.
  • End-to-end integration tests in CIintegration_tests/tests/token_authority.rs (five test cases) runs under cargo nextest against the standalone sequencer harness. Validated locally with cargo nextest run -p integration_tests --test token_authority -j 1 (5/5 PASS).
  • CI green on default branch — Pending CI run on the PR; will resolve when GitHub Actions checks land on this Draft.
  • README end-to-end usage — Pending Day 5. The existing README.md covers basic Token program use; a new section will document the authority lifecycle (deployment, addresses, CLI commands for mint / rotate / revoke).
  • Reproducible demo with RISC0_DEV_MODE=0demo.sh runs the full mint → transfer → rotate → revoke → mint-fails sequence with RISC0_DEV_MODE=0 and a clean docker-compose state. End-state holder balance is 1500 after the rotated-admin mints 500 onto an initial 1000 mint.
  • Narrated video walkthrough — Pending Day 6 (final-mile work before flipping Draft → Ready).

FURPS Self-Assessment

Functionality

Three new instruction variants (MintWithAuthority, RotateAuthority, RevokeAuthority) plus the authority-aware definition constructor NewFungibleDefinitionWithAuthority. Pre-existing Mint, Transfer, NewFungibleDefinition, InitializeAccount, Burn, PrintNft, and NewDefinitionWithMetadata are unchanged. The authority model supports the three Solana-style patterns: fixed supply (mint then revoke), variable supply (active authority), and authority handoff (rotate). The renounced state is terminal — once revoked, no instruction can re-introduce an authority on that definition.

Limitation: the authority lives only on TokenDefinition::Fungible (not on the non-fungible variant). The non-fungible path was out of scope for this prize.

Usability

Wallet integration exposes typed program_facades/token.rs builders and CLI subcommands for mint-with-authority, rotate-authority, and revoke-authority. The CLI mirrors the existing Token-program subcommand idiom; no new flags or env vars beyond the existing wallet setup are required.

The IDL story is the most reviewer-visible Usability tradeoff. Quoting docs/SPEL_STATUS.md:

This solution ships two IDL files at artifacts/:

  • artifacts/token.idl.spel.jsonspel generate-idl (real SPEL toolchain, v0.4.0) — Provenance — proves the program shape parses through SPEL's macro grammar
  • artifacts/token.idl.json — hand-authored against the SpelIdl schema in spel-framework-coreCompleteness — includes shapes the v0.4.0 CLI does not yet emit (e.g. the errors table for ApprovalError::{Unauthorized, Renounced})

The straightforward path — annotate program_methods/guest/src/bin/token.rs directly with #[spel_framework::lez_program], depend on spel-framework from the workspace, and let the macro emit the IDL during the normal guest build — does not compile in this checkout. The reason is a dep-graph collision between two versions of the same crate, plus a downstream cross-compile failure that the collision makes structurally difficult to fix.

Reviewers can reproduce the SPEL-emitted IDL via:

cargo install --git https://github.com/logos-co/spel --tag v0.4.0 spel
spel -- generate-idl spel-sidecar > artifacts/token.idl.spel.json

Reliability

The single-admin state machine has three states (Some(admin), transitioning Some(admin) → Some(new_admin) via rotate, and the terminal None via revoke) with no intermediate or unreachable states. The panic-on-failure semantics inherited from the LEZ guest convention mean every authorization failure is an unrecoverable rejection that the prover catches; there is no path to a partially-rotated or partially-revoked state. The five integration tests in integration_tests/tests/token_authority.rs cover: mint-with-active-authority, mint-by-wrong-signer-panics, rotate-then-new-admin-mints, revoke-then-mint-panics, and rotate-after-revoke-panics.

Performance

CU cost measurement is pending (Day 5). Expected pattern: the three new instructions add one is_authorized check and one AccountId equality check per gated call, on top of the baseline Mint's arithmetic. Wall-clock proof-generation overhead should be in the noise relative to the baseline.

Supportability

  • Test coverage: Unit tests in programs/token/src/tests.rs cover the Authority state machine transitions and the gate / rotate / revoke primitives in isolation (8 tests across the rotate / revoke / mint-with-authority paths). Integration tests in integration_tests/tests/token_authority.rs cover the end-to-end lifecycle against a real standalone sequencer (5 tests).
  • CI: Existing cargo nextest harness; new test file slots into the existing integration-test workflow without additional configuration.
  • Deployment: demo.sh is the canonical end-to-end deployment + exercise script. Reviewers can run it from a clean checkout with RISC0_DEV_MODE=0 ./demo.sh.
  • Code structure: Authority logic is split between the agnostic lez-approval/ crate (reusable) and the Token-program-specific handlers in programs/token/src/{new_definition,mint,rotate}.rs. The lez-approval boundary makes the same pattern reusable for any future LEZ program that wants single-admin gating.

Supporting Materials

Terms & Conditions

By submitting this solution, I confirm that I have read and agree to the Terms & Conditions.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2026

✅ Validation passed

A reviewer will assess against the prize criteria.
ℹ️ Solution submission for LP-0013.
ℹ️ Checking repo: https://github.com/ego-errante/logos-execution-zone


Automated check. See solution template and TERMS.

- Bump linked commit: c9be6e86 → 9e222602 (Day-5 polish: cycle bench,
  README expansion, fmt/clippy fixes).
- Tick "Compute unit (CU) cost documentation" (cycle counts captured
  via cycle_executor bin, documented in docs/CYCLE_COSTS.md + README).
- Tick "README end-to-end usage" (LP-0013 README section now covers
  deployment, CLI, cycle table, error codes, two-IDL pointer).
@ego-errante
Copy link
Copy Markdown
Author

Day-5 polish landed (HEAD 9e222602 on ego-errante/logos-execution-zone:lp-0013-token-authorities):

  • Compute unit / cycle cost documentationintegration_tests/src/bin/cycle_executor.rs captures risc0_zkvm::default_executor cycle counts for all three new instructions. Summary table in README.md; raw measurement log in docs/CYCLE_COSTS.md (date, machine, ELF sha256, repo HEAD pinned for reproducibility). Numbers: MintWithAuthority 154 858 cycles, RotateAuthority 127 350 cycles, RevokeAuthority 103 913 cycles (single-segment, 2^18 padded).
  • README end-to-end usage — LP-0013 section now covers standalone deployment, copy-pasteable CLI walkthrough for all four LP-0013 commands, the cycle table, ApprovalError::{Unauthorized, Renounced} panic-payload table, two-IDL pointer to docs/SPEL_STATUS.md, and explicit limitations.
  • Local CI sanitycargo +nightly fmt --check and cargo clippy --all-targets -- -D warnings clean across the LP-0013 surface (lez-approval, programs/token, wallet/cli, examples/{fixed,variable}-supply, integration_tests).
  • Validator note — Repo default branch on the fork has been moved to lp-0013-token-authorities so the lambda-prize validator can see demo.sh and artifacts/token.idl{,.spel}.json at the repo root.

Ready for review pending the narrated video walkthrough (Day 6 — required to flip Draft → Ready per the prize spec). All other success criteria are addressed and the corresponding checkboxes in solutions/LP-0013.md have been ticked.

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