Skip to content

Solution: LP-0013 - Token Program Improvements: Authorities#125

Open
bristinWild wants to merge 20 commits into
logos-blockchain:mainfrom
bristinWild:solution/lp-0013-authorities
Open

Solution: LP-0013 - Token Program Improvements: Authorities#125
bristinWild wants to merge 20 commits into
logos-blockchain:mainfrom
bristinWild:solution/lp-0013-authorities

Conversation

@bristinWild

@bristinWild bristinWild commented May 27, 2026

Copy link
Copy Markdown

Implements LP-0013: mint authority model for the LEZ token program.

What's implemented

  • lez-authority/ — standalone agnostic AuthoritySlot library (RFP-001)
  • mint_authority: Option<[u8; 32]> field on TokenDefinition::Fungible
  • NewFungibleDefinitionWithAuthority instruction
  • SetAuthority instruction — atomic rotation and permanent revocation
  • Mint updated — authority-gated, deterministic rejection when revoked
  • Guest binary wired with all new instructions
  • artifacts/token-idl.json regenerated via idl-gen
  • 2 E2E integration tests for authority lifecycle
  • 119 unit tests passing across all programs
  • docs/LP-0013-README.md — architecture, CLI usage, CU costs, program ID
  • scripts/demo-full-flow.sh — end-to-end demo with RISC0_DEV_MODE=0
  • scripts/examples/ — fixed supply + variable supply examples

##Demo video

Link - https://youtu.be/4E7lBgdNwb4

Original submission

logos-co/lambda-prize#56 (as requested by @fryorcraken)

bristinWild added 5 commits May 27, 2026 15:04
- Add lez-authority crate: agnostic AuthoritySlot library (RFP-001)
- Add mint_authority field to TokenDefinition::Fungible
- Add NewFungibleDefinitionWithAuthority instruction
- Add SetAuthority instruction (rotation + permanent revocation)
- Update Mint to enforce authority guard
- Wire new instructions into guest binary
- Add 8 authority unit tests (53 total passing)
- Add LP-0013 README, IDL, demo script, and example scripts
@bristinWild bristinWild marked this pull request as ready for review May 28, 2026 07:59
@bristinWild

Copy link
Copy Markdown
Author

Hi @fryorcraken, workflow approval needed for CI to run on this PR when you get a chance. Thanks!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements LP-0013 mint-authority support for the token program by extending the fungible token definition with an optional mint_authority, adding creation + authority-management instructions, wiring guest dispatch/IDL, and adding docs/tests/scripts demonstrating the authority lifecycle.

Changes:

  • Add mint_authority: Option<[u8; 32]> to TokenDefinition::Fungible, plus NewFungibleDefinitionWithAuthority and SetAuthority instructions.
  • Update mint to reject minting when authority is revoked (mint_authority == None) and add unit/integration tests for the authority lifecycle.
  • Add the standalone lez-authority crate, regenerate IDL artifacts, and add docs/demo/example scripts for LP-0013.

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
token-authority.idl.json Adds an additional IDL-like JSON file describing the token program + authority instructions.
scripts/examples/variable_supply_token.sh Example script for authority rotation on a variable-supply token.
scripts/examples/fixed_supply_token.sh Example script for revoking authority to create a fixed-supply token.
scripts/demo-full-flow.sh End-to-end demo script using lgs + spel to exercise the authority lifecycle.
programs/token/src/tests.rs Updates fixtures for new mint_authority field and adds authority unit tests.
programs/token/src/set_authority.rs New handler for rotating/revoking mint authority.
programs/token/src/new_definition.rs Initializes mint_authority for existing creation flows and adds new_fungible_definition_with_authority.
programs/token/src/mint.rs Adds rejection when mint authority is revoked.
programs/token/src/lib.rs Exposes the new set_authority module.
programs/token/src/burn.rs Updates fungible match to include mint_authority.
programs/token/methods/guest/src/bin/token.rs Wires guest entrypoints for new_fungible_definition_with_authority and set_authority.
programs/token/core/src/lib.rs Extends core instruction enum + token definition with mint_authority.
programs/stablecoin/src/tests.rs Updates token definition test fixtures to include mint_authority: None.
programs/integration_tests/tests/token.rs Adds integration tests for “create with authority” and “revoke authority”.
programs/integration_tests/tests/stablecoin.rs Updates token definition fixtures to include mint_authority: None.
programs/integration_tests/tests/ata.rs Updates token definition fixtures to include mint_authority: None.
programs/integration_tests/tests/amm.rs Updates token definition fixtures and a fungible definition matcher to include mint_authority.
programs/ata/src/tests.rs Updates token definition fixtures to include mint_authority: None.
programs/amm/src/tests.rs Updates token definition fixtures to include mint_authority: None.
programs/amm/src/new_definition.rs Updates LP token definition to include mint_authority: None.
lez-authority/src/lib.rs New program-agnostic AuthoritySlot library + unit tests.
lez-authority/Cargo.toml Adds crate manifest for lez-authority.
docs/LP-0013-README.md New design/usage documentation for LP-0013, including CLI + demo instructions.
Cargo.toml Adds lez-authority to the workspace members.
Cargo.lock Locks the new workspace crate dependency graph.
artifacts/token-idl.json Regenerated IDL including the new instruction(s) and mint_authority field.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread programs/token/src/mint.rs Outdated
Comment thread programs/token/src/set_authority.rs Outdated
Comment thread docs/LP-0013-README.md
Comment thread scripts/examples/variable_supply_token.sh Outdated
Comment thread scripts/demo-full-flow.sh Outdated
Comment thread scripts/demo-full-flow.sh Outdated
Comment thread scripts/demo-full-flow.sh Outdated
Comment thread scripts/demo-full-flow.sh Outdated
Comment thread token-authority.idl.json Outdated
Comment thread scripts/demo-full-flow.sh Outdated
bristinWild added 3 commits June 2, 2026 02:03
- mint.rs: validate caller account_id matches stored mint_authority key
- set_authority.rs: validate caller matches mint_authority before rotation/revoke
- tests.rs: align AUTHORITY constant and fixtures to match account_id [15; 32]
- demo-full-flow.sh: fix --public flag, remove || true from spel commands,
  update test count to 60
- mint.rs: validate caller account_id matches stored mint_authority key
- set_authority.rs: validate caller matches mint_authority before rotation/revoke
- tests.rs: align AUTHORITY constant and fixtures to account_id [15; 32]
- integration_tests/token.rs: derive authority_key from Ids::token_definition()
  so stored key matches actual signer account ID; update all affected asserts
- demo-full-flow.sh: fix --public flag, remove || true from spel commands,
  update test count to 60

60 unit tests + 16 integration tests passing (RISC0_DEV_MODE=1)
@bristinWild

bristinWild commented Jun 1, 2026

Copy link
Copy Markdown
Author

Addressed all Copilot review items and integration test gaps:
High (both fixed):

mint.rs: now validates account_id == mint_authority key before allowing mint - previously only checked is_some(), so anyone controlling the definition account could mint regardless of who the stored authority was
set_authority.rs: now validates caller matches stored key before rotation/revoke - previously only checked is_authorized, so the stored key was metadata only and never enforced

Medium:

Demo script: fixed account new --public flag (was missing --, would produce empty account IDs on a clean machine)
Demo script: removed || true from spel transaction commands so failures surface instead of being silently swallowed
Demo script: updated test count to 60

Integration tests (same root cause as unit tests):

authority_key now derived from Ids::token_definition() (real signer account ID derived from PrivateKey([10; 32])) instead of hardcoded [9; 32] - the old value never matched the actual signer so SetAuthority and Mint would have failed against the live sequencer
All affected asserts updated to reflect correct post-state

Style:

Replaced .unwrap() with .expect() on try_into() calls per clippy -D unwrap_used
Removed trailing newline in set_authority.rs

Results: 60 unit tests + 16 integration tests passing (RISC0_DEV_MODE=1 and RISC0_DEV_MODE=0), fmt and clippy clean.

@3esmit

3esmit commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

I will let Copilot review until it generates no more comments than I will review it

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 26 changed files in this pull request and generated 6 comments.

Comment thread programs/token/src/mint.rs Outdated
Comment thread programs/token/src/set_authority.rs Outdated
Comment thread token-authority.idl.json Outdated
Comment thread lez-authority/src/lib.rs Outdated
Comment thread scripts/examples/variable_supply_token.sh Outdated
Comment thread programs/token/src/new_definition.rs Outdated
bristinWild added 4 commits June 3, 2026 01:10
…fied spel flow

- replace unverified 'lgs wallet -- token' subcommands with the same spel
  invocations as demo-full-flow.sh (correct flags, base58->hex authority)
- use the new --authority-account signer model
- remove misleading || true error-swallowing and false 'verified' claims;
  point to the unit/integration tests that actually prove the guarantees
- new_fungible_definition_with_authority rejects all-zero mint_authority (RFP-001 reliability)
- add test_new_fungible_definition_with_authority_rejects_zero_authority
- restore demo-full-flow.sh (had been overwritten with example content); now
  uses the correct account parsing, base58->hex authority, and --authority-account flag
- commit updated Cargo.lock files for the lez-authority dependency
@bristinWild

bristinWild commented Jun 2, 2026

Copy link
Copy Markdown
Author

Pushed a substantial refactor addressing all Copilot review items (commits 308de620b8d4c2):

  1. Authority model reworked to a proper signer check. mint and set_authority now take an explicit authority_account that must sign, with the check routed through lez-authority's AuthoritySlot::check/set so authorization is against the actual transaction signer, not the definition account's own ID. This also makes the token program genuinely consume lez-authority (RFP-001) rather than reimplementing inline.
  2. Reject all-zero mint_authority at creation (RFP-001 reliability).
  3. Removed the stale root-level token-authority.idl.json; regenerated artifacts/token-idl.json.
  4. Rewrote both example scripts to use the verified spel flow and removed the misleading verification claims.
  5. 56 unit + 16 integration tests passed under fmt + clippy clean; IDL check passes locally.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 27 changed files in this pull request and generated 4 comments.

Comment thread programs/token/src/set_authority.rs Outdated
Comment thread docs/LP-0013-README.md Outdated
Comment thread programs/token/core/src/lib.rs Outdated
Comment thread docs/LP-0013-README.md
@0x-r4bbit

Copy link
Copy Markdown
Collaborator

Hey @bristinWild !

Thank you for the PR.
Just wanted to let you know that we're reviewing this!

- set_authority rejects all-zero new_authority on rotation (matches creation guard)
- SetAuthority/Mint doc comments now list the required authority signer account
- README: add --authority-account to mint/set-authority CLI examples,
  correct error-code table to actual panic strings, make program ID build-dependent
@bristinWild

bristinWild commented Jun 3, 2026

Copy link
Copy Markdown
Author

Hey @3esmit @0x-r4bbit , thank you for reviewing .

Just pushed some slight refactor addressing all Copilot review points.

Thank you.

@0x-r4bbit 0x-r4bbit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so I went through this and found a couple of things that are worth looking into.

@gravityblast @3esmit please review these comments as well.

TLDR:

  • I think the lez-authority should, if possible and feasible provide types/traits to extend program types and provide they authority functionality
  • I don't think we should introduce a new instruction to add authorities, and rather make it mandatory in the normal one
  • Currently, this breaks the AMM because it loses authority over its LP minting

Comment thread programs/amm/src/new_definition.rs Outdated
name: String::from("LP Token"),
total_supply: MINIMUM_LIQUIDITY,
metadata_id: None,
mint_authority: None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a problem.
The AMM creates a fungible token definition and has its mint_authority set to None which translates to minting being "revoked".

But after pool creation, we do need to mint more tokens in the future when liquidty is added to the pool.

The pool definition account (which is a PDA controlled by the AMM), should have authority to mint more tokens.

Comment thread programs/token/core/src/lib.rs Outdated
initial_supply: u128,
/// The initial mint authority. Can be rotated or revoked later via SetAuthority.
mint_authority: [u8; 32],
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can fold this into NewFungibleDefinition, where we would decide with mint_authority: Option whether there's a fixed supply or not (or if authority has been removed).

Comment thread programs/token/core/src/lib.rs Outdated
total_supply: u128,
metadata_id: Option<AccountId>,
/// Mint authority. `None` = supply is permanently fixed (no further minting allowed).
/// Added by LP-0013.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: let's remove the LP-0013 related comment here

Comment thread programs/token/src/mint.rs Outdated
.try_into()
.expect("AccountId is always 32 bytes");
let slot = AuthoritySlot(*mint_authority);
slot.check(signer).expect("Mint authority check failed");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this, it seems like AuthoritySlot isn't actually buying us much.
All we're doing here is wrapping the option so we can call check and rotate_authority etc on it.

I think to properly integrate this authority type, it needs to be used in the TokenDefinition itself.
Something along the lines of:

#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Eq, Debug)]
pub struct Authority(Option<[u8; 32]>);

impl Authority {
    pub fn authority(&self) -> Option<[u8; 32]> { self.0 }
    pub fn check(&self, signer: [u8; 32]) -> Result<(), AuthorityError> { /* … */ }
    pub fn rotate(&mut self, signer: [u8; 32], new: Option<[u8; 32]>) -> Result<(), AuthorityError> { /* … */ }
}

A program "inherits the owner slot" by embedding it:

enum TokenDefinition {
    Fungible { name: String, total_supply: u128, metadata_id: Option<AccountId>, authority: Authority },
    NonFungible { /* … */ },
}

Then maybe, we can have an Ownable trait:

pub trait Ownable {
    fn authority(&self) -> &Authority;
    fn authority_mut(&mut self) -> &mut Authority;

    fn require_owner(&self, signer: [u8; 32]) -> Result<(), AuthorityError> {
        self.authority().require(signer)
    }
    fn transfer_ownership(&mut self, signer: [u8; 32], new: [u8; 32]) -> Result<(), AuthorityError> {
        self.authority_mut().rotate(signer, Some(new))
    }
    fn renounce_ownership(&mut self, signer: [u8; 32]) -> Result<(), AuthorityError> {
        self.authority_mut().rotate(signer, None)
    }
}

Then, maybe we can do something like:

impl Ownable for TokenDefinition {
    fn authority(&self) -> &Authority { /* return the field, panic/NFT-guard as needed */ }
    fn authority_mut(&mut self) -> &mut Authority { /* … */ }
}

Comment thread programs/token/src/new_definition.rs Outdated
holding_target_account: AccountWithMetadata,
name: String,
initial_supply: u128,
mint_authority: [u8; 32],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this an AccountId

Comment thread programs/token/src/set_authority.rs Outdated
pub fn set_authority(
definition_account: AccountWithMetadata,
authority_account: AccountWithMetadata,
new_authority: Option<[u8; 32]>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be an AccountId

Comment thread programs/token/src/mint.rs Outdated
.expect("Token Definition account must be valid");

// LP-0013 / RFP-001: gate minting through lez-authority. The authority_account
// is the signer and must match the stored mint authority.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this comment.

@0x-r4bbit

Copy link
Copy Markdown
Collaborator

In fact, the integration tests uncover some of the issues I've commented about as well.

… LP minting

Addresses @0x-r4bbit's review:

- lez-authority now provides an Authority(Option<[u8;32]>) newtype and an
  Ownable trait (require_owner / transfer_ownership / renounce_ownership);
  programs embed the authority slot in their account type instead of calling
  a wrapper. Replaces the old AuthoritySlot.
- TokenDefinition::Fungible embeds authority: Authority; TokenDefinition
  implements Ownable.
- Fold mint authority into NewFungibleDefinition { mint_authority: Option<AccountId> };
  remove the separate NewFungibleDefinitionWithAuthority instruction.
- mint/set_authority authorize against the definition account itself (its id
  must match the stored authority and be authorized in the tx), restoring the
  2-account mint shape and supporting PDA authorities.
- Fix AMM: the pool-definition PDA is now the LP token's mint authority, so the
  AMM mints LP at creation and on add-liquidity (was permanently revoked).
- Instruction params use AccountId; remove LP-0013-specific comments.
- Regenerate token/amm/ata/stablecoin IDLs.

Tests: lez-authority 8, token unit 56, token/amm/stablecoin/ata integration all
green under RISC0_DEV_MODE=1; fmt + clippy clean.
@bristinWild bristinWild requested a review from 0x-r4bbit June 5, 2026 21:47
@bristinWild

Copy link
Copy Markdown
Author

Thanks for the detailed review, @0x-r4bbit

this was the right direction. Pushed a redesign addressing all points:

Authority type + Ownable trait: lez-authority now exposes an Authority(Option<[u8;32]>) newtype (authority() / require() / rotate()) and an Ownable trait with require_owner / transfer_ownership / renounce_ownership, matching your sketch. TokenDefinition::Fungible embeds authority: Authority and TokenDefinition implements Ownable (with an NFT guard). The old AuthoritySlot wrapper is gone.

Folded the instruction: removed NewFungibleDefinitionWithAuthority; NewFungibleDefinition now takes mint_authority: Option , Some = mintable, None = fixed supply.

AMM fix: the pool-definition PDA is now the LP token's mint authority (mint_authority: Some(pool_definition_lp.account_id)), so the AMM can mint LP at creation and when liquidity is added. mint/set_authority no longer take a separate authority account - they authorize against the definition account itself (id must match stored authority + be authorized in the tx), which works for both external owners and program PDAs. All AMM integration tests pass again.

Nits: instruction params use AccountId; removed the LP-0013 doc/code comments.
One interpretation note: I kept lez-authority itself [u8;32]-based (no nssa_core dependency, per the agnostic goal) and convert AccountId ↔ [u8;32] at the token program's instruction boundary. Happy to adjust if you'd prefer the lib depend on AccountId directly.

All suites green (lez-authority, token unit, and token/amm/stablecoin/ata integration under RISC0_DEV_MODE=1); fmt + clippy clean; all four IDLs regenerated.

@bristinWild

Copy link
Copy Markdown
Author

Also pushed 367ae38 , a cargo fmt fix for indentation on two AMM test lines. Unit Tests CI failure is a RISC0 toolchain 504 (GitHub infra flakiness) , a re-run should clear it.

pool_lp_created() and pool_lp_created_after_lock() fixtures now use
Authority::new(token_lp_definition_id) matching the production change
where the pool-definition PDA is the LP token's mint authority.
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.

4 participants