refactor: gate mint/set_authority via lez-authority with explicit signer account

This commit is contained in:
bristinWild 2026-06-03 01:10:08 +05:30
parent 029f617737
commit 175c9d256c
9 changed files with 250 additions and 131 deletions

7
Cargo.lock generated
View File

@ -2251,13 +2251,6 @@ dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "lez-authority"
version = "0.1.0"
dependencies = [
"borsh",
]
[[package]]
name = "libc"
version = "0.2.186"

View File

@ -139,6 +139,12 @@
"signer": true,
"init": false
},
{
"name": "authority_account",
"writable": false,
"signer": false,
"init": false
},
{
"name": "user_holding_account",
"writable": true,
@ -197,6 +203,12 @@
"writable": false,
"signer": false,
"init": false
},
{
"name": "authority_account",
"writable": false,
"signer": false,
"init": false
}
],
"args": [

View File

@ -28,6 +28,10 @@ impl Keys {
fn recipient_key() -> PrivateKey {
PrivateKey::try_new([12; 32]).expect("valid private key")
}
fn authority_key() -> PrivateKey {
PrivateKey::try_new([13; 32]).expect("valid private key")
}
}
impl Ids {
@ -50,6 +54,10 @@ impl Ids {
fn recipient() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&Keys::recipient_key()))
}
fn authority() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&Keys::authority_key()))
}
}
impl Accounts {
@ -62,7 +70,7 @@ impl Accounts {
total_supply: 1_000_000_u128,
metadata_id: None,
mint_authority: Some(
Ids::token_definition()
Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes"),
@ -81,7 +89,7 @@ impl Accounts {
total_supply: 1_000_000_u128,
metadata_id: None,
mint_authority: Some(
Ids::token_definition()
Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes"),
@ -114,6 +122,15 @@ impl Accounts {
nonce: Nonce(0),
}
}
fn authority_init() -> Account {
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::default(),
nonce: Nonce(0),
}
}
}
fn deploy_token(state: &mut V03State) {
@ -130,6 +147,7 @@ fn state_for_token_tests() -> V03State {
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
state.force_insert_account(Ids::holder(), Accounts::holder_init());
state.force_insert_account(Ids::recipient(), Accounts::recipient_init());
state.force_insert_account(Ids::authority(), Accounts::authority_init());
state
}
@ -138,6 +156,7 @@ fn state_for_token_tests_without_recipient() -> V03State {
deploy_token(&mut state);
state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init());
state.force_insert_account(Ids::holder(), Accounts::holder_init());
state.force_insert_account(Ids::authority(), Accounts::authority_init());
state
}
@ -429,7 +448,7 @@ fn token_burn() {
total_supply: 800_000_u128,
metadata_id: None,
mint_authority: Some(
Ids::token_definition()
Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes")
@ -463,13 +482,14 @@ fn token_mint() {
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::holder()],
vec![Ids::token_definition(), Ids::authority(), Ids::holder()],
vec![Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let witness_set =
public_transaction::WitnessSet::for_message(&message, &[&Keys::authority_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
@ -484,13 +504,13 @@ fn token_mint() {
total_supply: 1_500_000_u128,
metadata_id: None,
mint_authority: Some(
Ids::token_definition()
Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes")
),
}),
nonce: Nonce(1),
nonce: Nonce(0),
}
);
@ -522,7 +542,7 @@ fn token_mint_rejects_foreign_owned_definition() {
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::recipient()],
vec![Ids::token_definition(), Ids::authority(), Ids::recipient()],
vec![Nonce(0), Nonce(0)],
instruction,
)
@ -530,7 +550,7 @@ fn token_mint_rejects_foreign_owned_definition() {
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[&Keys::def_key(), &Keys::recipient_key()],
&[&Keys::authority_key(), &Keys::recipient_key()],
);
let tx = PublicTransaction::new(message, witness_set);
@ -556,13 +576,14 @@ fn token_mint_fresh_public_recipient_requires_authorization() {
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::recipient()],
vec![Ids::token_definition(), Ids::authority(), Ids::recipient()],
vec![Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let witness_set =
public_transaction::WitnessSet::for_message(&message, &[&Keys::authority_key()]);
let tx = PublicTransaction::new(message, witness_set);
assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err());
@ -587,7 +608,7 @@ fn token_mint_fresh_authorized_public_recipient() {
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::recipient()],
vec![Ids::token_definition(), Ids::authority(), Ids::recipient()],
vec![Nonce(0), Nonce(0)],
instruction,
)
@ -595,7 +616,7 @@ fn token_mint_fresh_authorized_public_recipient() {
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[&Keys::def_key(), &Keys::recipient_key()],
&[&Keys::authority_key(), &Keys::recipient_key()],
);
let tx = PublicTransaction::new(message, witness_set);
@ -611,13 +632,13 @@ fn token_mint_fresh_authorized_public_recipient() {
total_supply: 1_500_000_u128,
metadata_id: None,
mint_authority: Some(
Ids::token_definition()
Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes")
),
}),
nonce: Nonce(1),
nonce: Nonce(0),
}
);
@ -993,11 +1014,10 @@ fn token_new_fungible_definition_with_authority() {
fn token_set_authority_revoke() {
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
deploy_token(&mut state);
let authority_key: [u8; 32] = Ids::token_definition()
let authority_key: [u8; 32] = Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes");
// Create token with authority
let instruction = token_core::Instruction::NewFungibleDefinitionWithAuthority {
name: String::from("AuthCoin"),
@ -1017,18 +1037,23 @@ fn token_set_authority_revoke() {
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
// Seed the authority account so it can sign the revoke
state.force_insert_account(Ids::authority(), Accounts::authority_init());
// Revoke authority
let instruction = token_core::Instruction::SetAuthority {
new_authority: None,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition()],
vec![Nonce(1)],
vec![Ids::token_definition(), Ids::authority()],
vec![Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let witness_set =
public_transaction::WitnessSet::for_message(&message, &[&Keys::authority_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
assert_eq!(
@ -1042,7 +1067,7 @@ fn token_set_authority_revoke() {
metadata_id: None,
mint_authority: None,
}),
nonce: Nonce(2),
nonce: Nonce(1),
}
);
}

View File

@ -9,3 +9,4 @@ workspace = true
[dependencies]
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc6", features = ["host"], package = "lee_core" }
token_core = { path = "core" }
lez-authority = { path = "../../lez-authority" }

View File

@ -1,8 +1,8 @@
#![cfg_attr(not(test), no_main)]
use spel_framework::prelude::*;
use spel_framework::context::ProgramContext;
use nssa_core::account::AccountWithMetadata;
use spel_framework::context::ProgramContext;
use spel_framework::prelude::*;
#[cfg(not(test))]
risc0_zkvm::guest::entry!(main);
@ -25,11 +25,10 @@ mod token {
recipient: AccountWithMetadata,
amount_to_transfer: u128,
) -> SpelResult {
Ok(spel_framework::SpelOutput::execute(token_program::transfer::transfer(
sender,
recipient,
amount_to_transfer,
), vec![]))
Ok(spel_framework::SpelOutput::execute(
token_program::transfer::transfer(sender, recipient, amount_to_transfer),
vec![],
))
}
/// Create a new fungible token definition without metadata.
@ -111,11 +110,10 @@ mod token {
user_holding_account: AccountWithMetadata,
amount_to_burn: u128,
) -> SpelResult {
Ok(spel_framework::SpelOutput::execute(token_program::burn::burn(
definition_account,
user_holding_account,
amount_to_burn,
), vec![]))
Ok(spel_framework::SpelOutput::execute(
token_program::burn::burn(definition_account, user_holding_account, amount_to_burn),
vec![],
))
}
/// Mint new tokens to the holder's account.
@ -125,19 +123,22 @@ mod token {
ctx: ProgramContext,
#[account(mut, signer)]
definition_account: AccountWithMetadata,
#[account(mut)]
authority_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata,
amount_to_mint: u128,
) -> SpelResult {
Ok(spel_framework::SpelOutput::execute(token_program::mint::mint(
definition_account,
user_holding_account,
amount_to_mint,
ctx.self_program_id,
), vec![]))
Ok(spel_framework::SpelOutput::execute(
token_program::mint::mint(
definition_account,
authority_account,
user_holding_account,
amount_to_mint,
ctx.self_program_id,
),
vec![],
))
}
/// Create a new fungible token definition with a mint authority.
/// Unlike NewFungibleDefinition, this allows minting additional tokens later.
#[instruction]
@ -165,11 +166,13 @@ mod token {
#[instruction]
pub fn set_authority(
definition_account: AccountWithMetadata,
authority_account: AccountWithMetadata,
new_authority: Option<[u8; 32]>,
) -> SpelResult {
Ok(spel_framework::SpelOutput::execute(
token_program::set_authority::set_authority(
definition_account,
authority_account,
new_authority,
),
vec![],
@ -185,9 +188,9 @@ mod token {
#[account(init, signer)]
printed_account: AccountWithMetadata,
) -> SpelResult {
Ok(spel_framework::SpelOutput::execute(token_program::print_nft::print_nft(
master_account,
printed_account,
), vec![]))
Ok(spel_framework::SpelOutput::execute(
token_program::print_nft::print_nft(master_account, printed_account),
vec![],
))
}
}

View File

@ -1,3 +1,4 @@
use lez_authority::AuthoritySlot;
use nssa_core::{
account::{Account, AccountWithMetadata, Data},
program::{AccountPostState, Claim, ProgramId},
@ -6,14 +7,11 @@ use token_core::{TokenDefinition, TokenHolding};
pub fn mint(
definition_account: AccountWithMetadata,
authority_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata,
amount_to_mint: u128,
token_program_id: ProgramId,
) -> Vec<AccountPostState> {
assert!(
definition_account.is_authorized,
"Definition authorization is missing"
);
assert_eq!(
definition_account.account.program_owner, token_program_id,
"Token definition must be owned by token program"
@ -22,19 +20,22 @@ pub fn mint(
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
.expect("Token Definition account must be valid");
// LP-0013: enforce mint authority — minting is only allowed if mint_authority is Some.
// LP-0013 / RFP-001: gate minting through lez-authority. The authority_account
// is the signer and must match the stored mint authority.
if let TokenDefinition::Fungible { mint_authority, .. } = &definition {
match mint_authority {
None => panic!("Mint authority has been revoked; this token has a fixed supply"),
Some(authority_key) => {
assert_eq!(
definition_account.account_id.as_ref(),
authority_key,
"Signer is not the mint authority"
);
}
}
assert!(
authority_account.is_authorized,
"Mint authority must sign the transaction"
);
let signer: [u8; 32] = authority_account
.account_id
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes");
let slot = AuthoritySlot(*mint_authority);
slot.check(signer).expect("Mint authority check failed");
}
let mut holding = if user_holding_account.account == Account::default() {
TokenHolding::zeroized_from_definition(definition_account.account_id, &definition)
} else {
@ -86,6 +87,7 @@ pub fn mint(
vec![
AccountPostState::new(definition_post),
AccountPostState::new(authority_account.account),
AccountPostState::new_claimed_if_default(holding_post, Claim::Authorized),
]
}

View File

@ -1,38 +1,33 @@
use lez_authority::AuthoritySlot;
use nssa_core::{
account::{AccountWithMetadata, Data},
program::AccountPostState,
};
use token_core::TokenDefinition;
#[must_use]
pub fn set_authority(
definition_account: AccountWithMetadata,
authority_account: AccountWithMetadata,
new_authority: Option<[u8; 32]>,
) -> Vec<AccountPostState> {
assert!(
definition_account.is_authorized,
"Definition account authorization is missing; only the mint authority can call SetAuthority"
);
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
.expect("Token Definition account must be valid");
match &mut definition {
TokenDefinition::Fungible { mint_authority, .. } => {
match mint_authority {
None => {
panic!("Mint authority already revoked; supply is permanently fixed");
}
Some(authority_key) => {
// Validate caller matches the stored mint authority key
assert_eq!(
definition_account.account_id.as_ref(),
authority_key.as_ref(),
"Signer does not match the stored mint authority"
);
*mint_authority = new_authority;
}
}
assert!(
authority_account.is_authorized,
"Mint authority must sign the transaction"
);
let signer: [u8; 32] = authority_account
.account_id
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes");
let mut slot = AuthoritySlot(*mint_authority);
slot.set(signer, new_authority)
.expect("SetAuthority failed");
*mint_authority = slot.0;
}
TokenDefinition::NonFungible { .. } => {
panic!("SetAuthority is not supported for Non-Fungible Tokens");
@ -42,5 +37,8 @@ pub fn set_authority(
let mut definition_post = definition_account.account;
definition_post.data = Data::from(&definition);
vec![AccountPostState::new(definition_post)]
vec![
AccountPostState::new(definition_post),
AccountPostState::new(authority_account.account),
]
}

View File

@ -51,6 +51,16 @@ impl AccountForTests {
}
}
/// A signed authority account whose ID matches the [15; 32] mint authority
/// used by definition_account_auth() / definition_account_mint().
fn authority_account_auth() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: IdForTests::pool_definition_id(),
}
}
fn definition_account_foreign_owner() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
@ -904,6 +914,7 @@ fn test_mint_not_valid_holding_account() {
let holding_account = AccountForTests::definition_account_without_auth();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
@ -917,6 +928,7 @@ fn test_mint_not_valid_definition_account() {
let holding_account = AccountForTests::holding_same_definition_without_authorization();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
@ -924,12 +936,19 @@ fn test_mint_not_valid_definition_account() {
}
#[test]
#[should_panic(expected = "Definition authorization is missing")]
#[should_panic(expected = "Mint authority must sign the transaction")]
fn test_mint_missing_authorization() {
let definition_account = AccountForTests::definition_account_without_auth();
let definition_account = AccountForTests::definition_account_auth();
let holding_account = AccountForTests::holding_same_definition_without_authorization();
// authority account that is NOT signed
let unsigned_authority = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
account_id: IdForTests::pool_definition_id(),
};
let _post_states = mint(
definition_account,
unsigned_authority,
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
@ -943,6 +962,7 @@ fn test_mint_rejects_foreign_owned_definition() {
let holding_account = AccountForTests::holding_account_uninit();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
@ -952,10 +972,12 @@ fn test_mint_rejects_foreign_owned_definition() {
#[test]
#[should_panic(expected = "Mismatch Token Definition and Token Holding")]
fn test_mint_mismatched_token_definition() {
//
let definition_account = AccountForTests::definition_account_auth();
let holding_account = AccountForTests::holding_different_definition();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
@ -968,12 +990,13 @@ fn test_mint_success() {
let holding_account = AccountForTests::holding_same_definition_without_authorization();
let post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
let [def_post, holding_post] = post_states.try_into().unwrap();
let [def_post, _authority_post, holding_post] = post_states.try_into().unwrap();
assert_eq!(
*def_post.account(),
@ -993,12 +1016,13 @@ fn test_mint_uninit_holding_success() {
let holding_account = AccountForTests::holding_account_uninit();
let post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
);
let [def_post, holding_post] = post_states.try_into().unwrap();
let [def_post, _authority_post, holding_post] = post_states.try_into().unwrap();
assert_eq!(
*def_post.account(),
@ -1019,6 +1043,7 @@ fn test_mint_total_supply_overflow() {
let holding_account = AccountForTests::holding_same_definition_without_authorization();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_overflow(),
TOKEN_PROGRAM_ID,
@ -1032,6 +1057,7 @@ fn test_mint_holding_account_overflow() {
let holding_account = AccountForTests::holding_same_definition_without_authorization_overflow();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_overflow(),
TOKEN_PROGRAM_ID,
@ -1045,6 +1071,7 @@ fn test_mint_cannot_mint_unmintable_tokens() {
let holding_account = AccountForTests::holding_account_master_nft();
let _post_states = mint(
definition_account,
AccountForTests::authority_account_auth(),
holding_account,
BalanceForTests::mint_success(),
TOKEN_PROGRAM_ID,
@ -1364,24 +1391,6 @@ mod authority_tests {
}
}
fn def_without_auth_flag() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
program_owner: [5_u32; 8],
balance: 0_u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("test"),
total_supply: 100_000_u128,
metadata_id: None,
mint_authority: Some(AUTHORITY),
}),
nonce: 0_u128.into(),
},
is_authorized: false,
account_id: AccountId::new([15; 32]),
}
}
fn holding_account() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
@ -1398,15 +1407,34 @@ mod authority_tests {
}
}
/// Signed authority matching the [15; 32] stored mint authority.
fn authority_signer() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([15; 32]),
}
}
/// A different signer (Bob) — NOT the current authority.
fn wrong_authority_signer() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([99; 32]),
}
}
#[test]
fn mint_with_authority_succeeds() {
let post_states = mint(
def_with_authority(),
authority_signer(),
holding_account(),
50_000,
TOKEN_PROGRAM_ID,
);
let [def_post, holding_post] = post_states.try_into().unwrap();
let [def_post, _authority_post, holding_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
let holding = TokenHolding::try_from(&holding_post.account().data).unwrap();
@ -1429,10 +1457,11 @@ mod authority_tests {
}
#[test]
#[should_panic(expected = "Mint authority has been revoked; this token has a fixed supply")]
#[should_panic(expected = "Mint authority check failed")]
fn mint_with_revoked_authority_fails() {
let _ = mint(
def_with_authority_revoked(),
authority_signer(),
holding_account(),
50_000,
TOKEN_PROGRAM_ID,
@ -1440,10 +1469,16 @@ mod authority_tests {
}
#[test]
#[should_panic(expected = "Definition authorization is missing")]
#[should_panic(expected = "Mint authority must sign the transaction")]
fn mint_without_is_authorized_fails() {
let unsigned_authority = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
account_id: AccountId::new([15; 32]),
};
let _ = mint(
def_without_auth_flag(),
def_with_authority(),
unsigned_authority,
holding_account(),
50_000,
TOKEN_PROGRAM_ID,
@ -1453,8 +1488,8 @@ mod authority_tests {
#[test]
fn set_authority_rotates_to_new_key() {
let new_key = [7_u8; 32];
let post_states = set_authority(def_with_authority(), Some(new_key));
let [def_post] = post_states.try_into().unwrap();
let post_states = set_authority(def_with_authority(), authority_signer(), Some(new_key));
let [def_post, _authority_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
assert!(matches!(
@ -1463,10 +1498,22 @@ mod authority_tests {
));
}
#[test]
#[should_panic(expected = "Mint authority check failed")]
fn mint_with_wrong_signer_fails() {
let _ = mint(
def_with_authority(),
wrong_authority_signer(),
holding_account(),
50_000,
TOKEN_PROGRAM_ID,
);
}
#[test]
fn set_authority_revokes_permanently() {
let post_states = set_authority(def_with_authority(), None);
let [def_post] = post_states.try_into().unwrap();
let post_states = set_authority(def_with_authority(), authority_signer(), None);
let [def_post, _authority_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
assert!(matches!(
@ -1479,22 +1526,41 @@ mod authority_tests {
}
#[test]
#[should_panic(expected = "Mint authority already revoked; supply is permanently fixed")]
#[should_panic(expected = "SetAuthority failed")]
fn set_authority_on_revoked_fails() {
let _ = set_authority(def_with_authority_revoked(), Some([7_u8; 32]));
let _ = set_authority(
def_with_authority_revoked(),
authority_signer(),
Some([7_u8; 32]),
);
}
#[test]
#[should_panic(expected = "Definition account authorization is missing")]
#[should_panic(expected = "Mint authority must sign the transaction")]
fn set_authority_without_is_authorized_fails() {
let _ = set_authority(def_without_auth_flag(), Some([7_u8; 32]));
let unsigned_authority = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
account_id: AccountId::new([15; 32]),
};
let _ = set_authority(def_with_authority(), unsigned_authority, Some([7_u8; 32]));
}
#[test]
#[should_panic(expected = "SetAuthority failed")]
fn set_authority_wrong_signer_fails() {
let _ = set_authority(
def_with_authority(),
wrong_authority_signer(),
Some([7_u8; 32]),
);
}
#[test]
fn set_authority_rotate_then_old_cannot_mint() {
let new_key = [7_u8; 32];
let post_states = set_authority(def_with_authority(), Some(new_key));
let [def_post] = post_states.try_into().unwrap();
let post_states = set_authority(def_with_authority(), authority_signer(), Some(new_key));
let [def_post, _authority_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
assert!(matches!(

View File

@ -46,6 +46,22 @@ TOKEN_BIN="${TOKEN_BIN:-$LEZ_PROGRAMS/target/riscv-guest/token-methods/token-gue
DEMO_DIR="${DEMO_DIR:-$(pwd)}"
WALLET_DIR="${WALLET_DIR:-$DEMO_DIR/.scaffold/wallet}"
# Convert a base58 "Public/..." account_id to the 64-char hex form
# that SPEL expects for [u8; 32] args (e.g. --mint-authority).
b58_to_hex() {
local id="${1#Public/}" # strip the Public/ prefix
id="${id#Private/}" # strip Private/ if present
python3 -c "
import sys
s = sys.argv[1]
alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
num = 0
for c in s:
num = num * 58 + alphabet.index(c)
print(num.to_bytes(32, 'big').hex())
" "$id"
}
echo "================================================================"
echo " LP-0013: Token Program Mint Authority — End-to-End Demo"
echo " RISC0_DEV_MODE=${RISC0_DEV_MODE:-not set}"
@ -66,17 +82,18 @@ lgs wallet topup 2>&1 | grep -E "complete|funded|Address" || true
echo " Wallet funded."
echo "[3/7] Creating token accounts..."
DEF_RESULT=$(lgs wallet -- account new --public 2>&1)
DEF_ID=$(echo "$DEF_RESULT" | grep -oE '[0-9a-f]{64}' | head -1)
SUPPLY_RESULT=$(lgs wallet -- account new --public 2>&1)
SUPPLY_ID=$(echo "$SUPPLY_RESULT" | grep -oE '[0-9a-f]{64}' | head -1)
RECIPIENT_RESULT=$(lgs wallet -- account new --public 2>&1)
RECIPIENT_ID=$(echo "$RECIPIENT_RESULT" | grep -oE '[0-9a-f]{64}' | head -1)
DEF_RESULT=$(lgs wallet -- account new public 2>&1)
DEF_ID=$(echo "$DEF_RESULT" | grep -oE 'account_id [^ ]+' | awk '{print $2}')
SUPPLY_RESULT=$(lgs wallet -- account new public 2>&1)
SUPPLY_ID=$(echo "$SUPPLY_RESULT" | grep -oE 'account_id [^ ]+' | awk '{print $2}')
RECIPIENT_RESULT=$(lgs wallet -- account new public 2>&1)
RECIPIENT_ID=$(echo "$RECIPIENT_RESULT" | grep -oE 'account_id [^ ]+' | awk '{print $2}')
echo " Definition account: $DEF_ID"
echo " Supply account: $SUPPLY_ID"
echo " Recipient account: $RECIPIENT_ID"
echo "[4/7] Creating token with mint authority..."
DEF_ID_HEX=$(b58_to_hex "$DEF_ID")
NSSA_WALLET_HOME_DIR="$WALLET_DIR" \
${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- new-fungible-definition-with-authority \
@ -84,7 +101,7 @@ ${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
--holding-target-account "$SUPPLY_ID" \
--name "DemoCoin" \
--initial-supply 1000000 \
--mint-authority "$DEF_ID"
--mint-authority "$DEF_ID_HEX"
echo " Token 'DemoCoin' submitted. Initial supply: 1,000,000"
sleep 2
@ -94,6 +111,7 @@ NSSA_WALLET_HOME_DIR="$WALLET_DIR" \
${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- mint \
--definition-account "$DEF_ID" \
--authority-account "$DEF_ID" \
--user-holding-account "$RECIPIENT_ID" \
--amount-to-mint 500000
echo " Mint transaction submitted. New total supply: 1,500,000"
@ -105,6 +123,7 @@ NSSA_WALLET_HOME_DIR="$WALLET_DIR" \
${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- set-authority \
--definition-account "$DEF_ID" \
--authority-account "$DEF_ID" \
--new-authority none
echo " Authority revoked. Supply permanently fixed at 1,500,000"