mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-03 05:29:50 +00:00
refactor: gate mint/set_authority via lez-authority with explicit signer account
This commit is contained in:
parent
029f617737
commit
175c9d256c
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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![],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
]
|
||||
}
|
||||
|
||||
@ -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),
|
||||
]
|
||||
}
|
||||
|
||||
@ -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!(
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user