Solution: LP-0013 — Token program authorities#62
Draft
ego-errante wants to merge 3 commits into
Draft
Conversation
✅ Validation passedA reviewer will assess against the prize criteria. 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).
Author
|
Day-5 polish landed (HEAD
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
lp-0013-token-authoritiesc9be6e86lez-approval/src/lib.rs— RFP-001 agnostic approval library (Authority,ApprovalError)programs/token/core/src/lib.rs—Instructionenum extensions and the canonicalAuthorityfield onTokenDefinition::Fungibleprograms/token/src/{new_definition,mint,rotate}.rs— handler implementationsprogram_methods/guest/src/bin/token.rs— guest dispatcherwallet/src/{program_facades,cli/programs}/token.rs— wallet facade + CLI subcommandsexamples/{fixed-supply,variable-supply}/— two example integrationsintegration_tests/tests/token_authority.rs— end-to-end lifecycle testsartifacts/token.idl.spel.json— SPEL-emitted IDL (provenance, generated fromspel-sidecar/)artifacts/token.idl.json— hand-authored canonical IDL (completeness, conforms toSpelIdl)spel-sidecar/— sidecar Cargo package outside the workspace that hosts the SPEL-shape mirrordocs/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 singleInstructionenum dispatched byprogram_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-existingMintandNewFungibleDefinitionpaths 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: Authorityfield onTokenDefinition::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 theauthorityfield is a breaking Borsh-layout change for pre-existingTokenDefinition::Fungibleaccounts; this is documented in theTokenDefinitiondoc comment with the deferredFungibleV2/ separate-PDA alternatives that would preserve in-flight schema compatibility if LEZ ever introduces such a guarantee.lez-approvalas the RFP-001 deliverableThe agnostic approval library is implemented as a new top-level workspace crate,
lez-approval. It exposes:Authority(Option<AccountId>)— a single-admin wrapper whoseNonestate is terminal (the renounced sentinel). TheOptionis intentionally hidden behind constructor (Authority::new,Authority::renounced) and predicate (Authority::is_renounced) methods so callers cannot accidentally reach into theNonestate 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.Unauthorizedcovers both "the wrong signer" and "the signer is not authorized at all";Renouncedcovers all post-revocation rejections.gate/rotate/revokecontract 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
ApprovalErrorpayload rather than returning aResult. This matches the surrounding code and avoids each handler having to writematch … panic!()boilerplate. The library is generic — it depends only onnssa_core::account::AccountIdand 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.
rotate_authorityeither updates the definition account'sauthorityfield or panics; there is no intermediate state where the authority isNonebut 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
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.Instruction::Mintto 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 onprogram_methods/guest/src/bin/token.rsto get a real-program SPEL IDL. Attempted and abandoned — pullingspel-frameworkinto the workspace causes a hardnssa_corev0.1.0 vs v0.2.0-rc3 dep-graph collision (#[lez_program]proc-macro emits path-literal references to whichevernssa_corecargo 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 indocs/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
NewFungibleDefinitionWithAuthoritysets the initial authority at definition creation;MintWithAuthoritymints gated by the recorded authority;RotateAuthorityandRevokeAuthoritycover rotation and terminal revocation (renounced state is a permanent sentinel viaAuthority::renounced()).examples/fixed-supply/(mint everything, then revoke) andexamples/variable-supply/(rotatable inflation), both runnable end-to-end.lez-approval/shipsAuthority,ApprovalError, and thegate/rotate/revokeprimitives. The crate depends only onnssa_core::account::AccountIdand is reusable by any LEZ program that wants single-admin gating.Usability
wallet/src/program_facades/token.rsexposes typed builders for the three new instructions;wallet/src/cli/programs/token.rswires them as CLI subcommands.demo.shexercises both surfaces end-to-end.artifacts/token.idl.spel.json(SPEL-emitted viaspel generate-idlagainst thespel-sidecar/sidecar — provenance) andartifacts/token.idl.json(hand-authored against theSpelIdlschema — completeness, including theerrorstable forApprovalError::{Unauthorized, Renounced}which the v0.4.0 CLI does not yet emit). Seedocs/SPEL_STATUS.mdfor the workaround rationale and reproducibility steps.Reliability
Authoritystate machine has no intermediate state:rotateis a single field write toauthority: Authority::new(new_admin), andrevokeis a single field write toauthority: Authority::renounced(). Covered by integration testsrotate_then_mint_succeedsandrevoke_after_authority_set_succeeds(both inintegration_tests/tests/token_authority.rs).MintWithAuthorityagainst a renounced definition panics withApprovalError::Renounced, a documented error variant inlez-approval/src/lib.rs. Covered by integration testmint_after_revoke_panics. The panic-on-failure path is the canonical LEZ guest rejection mechanism.Performance
MintWithAuthority,RotateAuthority,RevokeAuthority) relative to the baselineMintinstruction in the README before flipping this PR to Ready.Supportability
demo.shruns the full lifecycle against a docker-compose-spun standalone LEZ sequencer.integration_tests/tests/token_authority.rs(five test cases) runs undercargo nextestagainst the standalone sequencer harness. Validated locally withcargo nextest run -p integration_tests --test token_authority -j 1(5/5 PASS).README.mdcovers basic Token program use; a new section will document the authority lifecycle (deployment, addresses, CLI commands for mint / rotate / revoke).RISC0_DEV_MODE=0—demo.shruns the full mint → transfer → rotate → revoke → mint-fails sequence withRISC0_DEV_MODE=0and a clean docker-compose state. End-state holder balance is 1500 after the rotated-admin mints 500 onto an initial 1000 mint.FURPS Self-Assessment
Functionality
Three new instruction variants (
MintWithAuthority,RotateAuthority,RevokeAuthority) plus the authority-aware definition constructorNewFungibleDefinitionWithAuthority. Pre-existingMint,Transfer,NewFungibleDefinition,InitializeAccount,Burn,PrintNft, andNewDefinitionWithMetadataare 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.rsbuilders and CLI subcommands formint-with-authority,rotate-authority, andrevoke-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: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.jsonReliability
The single-admin state machine has three states (
Some(admin), transitioningSome(admin) → Some(new_admin)via rotate, and the terminalNonevia 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 inintegration_tests/tests/token_authority.rscover: 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_authorizedcheck and oneAccountIdequality check per gated call, on top of the baselineMint's arithmetic. Wall-clock proof-generation overhead should be in the noise relative to the baseline.Supportability
programs/token/src/tests.rscover theAuthoritystate machine transitions and the gate / rotate / revoke primitives in isolation (8 tests across the rotate / revoke / mint-with-authority paths). Integration tests inintegration_tests/tests/token_authority.rscover the end-to-end lifecycle against a real standalone sequencer (5 tests).cargo nextestharness; new test file slots into the existing integration-test workflow without additional configuration.demo.shis the canonical end-to-end deployment + exercise script. Reviewers can run it from a clean checkout withRISC0_DEV_MODE=0 ./demo.sh.lez-approval/crate (reusable) and the Token-program-specific handlers inprograms/token/src/{new_definition,mint,rotate}.rs. Thelez-approvalboundary makes the same pattern reusable for any future LEZ program that wants single-admin gating.Supporting Materials
docs/SPEL_STATUS.mdspel-sidecar/demo.shTerms & Conditions
By submitting this solution, I confirm that I have read and agree to the Terms & Conditions.