mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-03 13:39:38 +00:00
fix(token): support external mint authority; intentional metadata supply; demo path resolver
Addresses review on PR #125 (LP-0013): #1 authority transfer now hands control to the new signer. mint and set_authority take a trailing authority_accounts (Vec<AccountWithMetadata>): empty preserves the original self/PDA-authority behavior (AMM unchanged), one entry lets an external/rotated authority actually mint or rotate again. Tests: rotated_authority_can_mint, rotated_authority_old_key_cannot_mint. #2 metadata-backed fungibles take a real mint_authority instead of a hardcoded Authority::renounced(), matching the plain-fungible supply model. Test: test_metadata_fungible_with_authority_is_mintable. #3 demo-full-flow.sh resolves TOKEN_BIN from the README-documented cargo risczero build output, falling back to the workspace build, with an explicit TOKEN_BIN override still respected. Regenerated token-idl.json for the new trailing authority_accounts.
This commit is contained in:
parent
dd8328cf9f
commit
160ff8ee4a
@ -150,6 +150,13 @@
|
|||||||
"writable": true,
|
"writable": true,
|
||||||
"signer": false,
|
"signer": false,
|
||||||
"init": false
|
"init": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authority_accounts",
|
||||||
|
"writable": false,
|
||||||
|
"signer": false,
|
||||||
|
"init": false,
|
||||||
|
"rest": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
@ -167,6 +174,13 @@
|
|||||||
"writable": false,
|
"writable": false,
|
||||||
"signer": false,
|
"signer": false,
|
||||||
"init": false
|
"init": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authority_accounts",
|
||||||
|
"writable": false,
|
||||||
|
"signer": false,
|
||||||
|
"init": false,
|
||||||
|
"rest": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
|||||||
@ -90,6 +90,9 @@ pub enum NewTokenDefinition {
|
|||||||
Fungible {
|
Fungible {
|
||||||
name: String,
|
name: String,
|
||||||
total_supply: u128,
|
total_supply: u128,
|
||||||
|
/// Mint authority. `Some(id)` makes the token mintable by `id`; `None`
|
||||||
|
/// fixes the supply.
|
||||||
|
mint_authority: Option<AccountId>,
|
||||||
},
|
},
|
||||||
NonFungible {
|
NonFungible {
|
||||||
name: String,
|
name: String,
|
||||||
|
|||||||
@ -128,6 +128,7 @@ mod token {
|
|||||||
#[account(mut, signer)]
|
#[account(mut, signer)]
|
||||||
definition_account: AccountWithMetadata,
|
definition_account: AccountWithMetadata,
|
||||||
user_holding_account: AccountWithMetadata,
|
user_holding_account: AccountWithMetadata,
|
||||||
|
authority_accounts: Vec<AccountWithMetadata>,
|
||||||
amount_to_mint: u128,
|
amount_to_mint: u128,
|
||||||
) -> SpelResult {
|
) -> SpelResult {
|
||||||
Ok(spel_framework::SpelOutput::execute(
|
Ok(spel_framework::SpelOutput::execute(
|
||||||
@ -135,6 +136,7 @@ mod token {
|
|||||||
definition_account,
|
definition_account,
|
||||||
user_holding_account,
|
user_holding_account,
|
||||||
amount_to_mint,
|
amount_to_mint,
|
||||||
|
authority_accounts,
|
||||||
ctx.self_program_id,
|
ctx.self_program_id,
|
||||||
),
|
),
|
||||||
vec![],
|
vec![],
|
||||||
@ -147,10 +149,15 @@ mod token {
|
|||||||
#[instruction]
|
#[instruction]
|
||||||
pub fn set_authority(
|
pub fn set_authority(
|
||||||
definition_account: AccountWithMetadata,
|
definition_account: AccountWithMetadata,
|
||||||
|
authority_accounts: Vec<AccountWithMetadata>,
|
||||||
new_authority: Option<AccountId>,
|
new_authority: Option<AccountId>,
|
||||||
) -> SpelResult {
|
) -> SpelResult {
|
||||||
Ok(spel_framework::SpelOutput::execute(
|
Ok(spel_framework::SpelOutput::execute(
|
||||||
token_program::set_authority::set_authority(definition_account, new_authority),
|
token_program::set_authority::set_authority(
|
||||||
|
definition_account,
|
||||||
|
new_authority,
|
||||||
|
authority_accounts,
|
||||||
|
),
|
||||||
vec![],
|
vec![],
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ pub fn mint(
|
|||||||
definition_account: AccountWithMetadata,
|
definition_account: AccountWithMetadata,
|
||||||
user_holding_account: AccountWithMetadata,
|
user_holding_account: AccountWithMetadata,
|
||||||
amount_to_mint: u128,
|
amount_to_mint: u128,
|
||||||
|
authority_accounts: Vec<AccountWithMetadata>,
|
||||||
token_program_id: ProgramId,
|
token_program_id: ProgramId,
|
||||||
) -> Vec<AccountPostState> {
|
) -> Vec<AccountPostState> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -19,17 +20,22 @@ pub fn mint(
|
|||||||
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
||||||
.expect("Token Definition account must be valid");
|
.expect("Token Definition account must be valid");
|
||||||
|
|
||||||
// Minting is gated on the definition's mint authority: the definition account
|
// Minting is gated on the definition's stored mint authority. The proof of
|
||||||
// must be authorized in this transaction and its id must match the stored
|
// authority is whichever account is presented as authorized AND whose id
|
||||||
// authority. This holds for an external owner that signs the definition key,
|
// matches the stored authority:
|
||||||
// and for a program-controlled PDA authorized via its seeds (e.g. the AMM's
|
//
|
||||||
// pool definition minting LP tokens).
|
// - When `authority_accounts` is empty, the definition account itself must be the authority
|
||||||
|
// (self/PDA authority — e.g. the AMM's LP definition minting under its own seed). This is the
|
||||||
|
// original mint behavior.
|
||||||
|
// - When `authority_accounts` has one entry, that account is the external authority (e.g. a
|
||||||
|
// rotated owner key). This lets a transferred authority actually mint, as RFP-001 requires.
|
||||||
if let TokenDefinition::Fungible { .. } = &definition {
|
if let TokenDefinition::Fungible { .. } = &definition {
|
||||||
|
let authority = authority_accounts.first().unwrap_or(&definition_account);
|
||||||
assert!(
|
assert!(
|
||||||
definition_account.is_authorized,
|
authority.is_authorized,
|
||||||
"Mint authority must authorize the transaction"
|
"Mint authority must authorize the transaction"
|
||||||
);
|
);
|
||||||
let signer: [u8; 32] = definition_account
|
let signer: [u8; 32] = authority
|
||||||
.account_id
|
.account_id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.try_into()
|
.try_into()
|
||||||
@ -88,8 +94,17 @@ pub fn mint(
|
|||||||
let mut holding_post = user_holding_account.account;
|
let mut holding_post = user_holding_account.account;
|
||||||
holding_post.data = Data::from(&holding);
|
holding_post.data = Data::from(&holding);
|
||||||
|
|
||||||
vec![
|
// Post-states must match pre-state order and count. Pre-state order is
|
||||||
AccountPostState::new(definition_post),
|
// [definition, holding, ...authority_accounts]; authority accounts are
|
||||||
AccountPostState::new_claimed_if_default(holding_post, Claim::Authorized),
|
// read-only and pass through unchanged.
|
||||||
]
|
let mut post_states = Vec::with_capacity(authority_accounts.len().saturating_add(2));
|
||||||
|
post_states.push(AccountPostState::new(definition_post));
|
||||||
|
post_states.push(AccountPostState::new_claimed_if_default(
|
||||||
|
holding_post,
|
||||||
|
Claim::Authorized,
|
||||||
|
));
|
||||||
|
for authority in authority_accounts {
|
||||||
|
post_states.push(AccountPostState::new(authority.account));
|
||||||
|
}
|
||||||
|
post_states
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,12 +116,16 @@ pub fn new_definition_with_metadata(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let (token_definition, token_holding) = match new_definition {
|
let (token_definition, token_holding) = match new_definition {
|
||||||
NewTokenDefinition::Fungible { name, total_supply } => (
|
NewTokenDefinition::Fungible {
|
||||||
|
name,
|
||||||
|
total_supply,
|
||||||
|
mint_authority,
|
||||||
|
} => (
|
||||||
TokenDefinition::Fungible {
|
TokenDefinition::Fungible {
|
||||||
name,
|
name,
|
||||||
total_supply,
|
total_supply,
|
||||||
metadata_id: Some(metadata_target_account.account_id),
|
metadata_id: Some(metadata_target_account.account_id),
|
||||||
authority: Authority::renounced(),
|
authority: authority_from(mint_authority),
|
||||||
},
|
},
|
||||||
TokenHolding::Fungible {
|
TokenHolding::Fungible {
|
||||||
definition_id: definition_target_account.account_id,
|
definition_id: definition_target_account.account_id,
|
||||||
|
|||||||
@ -8,20 +8,23 @@ use token_core::TokenDefinition;
|
|||||||
pub fn set_authority(
|
pub fn set_authority(
|
||||||
definition_account: AccountWithMetadata,
|
definition_account: AccountWithMetadata,
|
||||||
new_authority: Option<AccountId>,
|
new_authority: Option<AccountId>,
|
||||||
|
authority_accounts: Vec<AccountWithMetadata>,
|
||||||
) -> Vec<AccountPostState> {
|
) -> Vec<AccountPostState> {
|
||||||
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
let mut definition = TokenDefinition::try_from(&definition_account.account.data)
|
||||||
.expect("Token Definition account must be valid");
|
.expect("Token Definition account must be valid");
|
||||||
|
|
||||||
match &mut definition {
|
match &mut definition {
|
||||||
TokenDefinition::Fungible { .. } => {
|
TokenDefinition::Fungible { .. } => {
|
||||||
// The current mint authority must authorize this transaction: the
|
// The current mint authority must authorize this transaction. As in
|
||||||
// definition account must be authorized and its id must match the
|
// `mint`, the proof is either the definition account itself (empty
|
||||||
// stored authority.
|
// `authority_accounts`, self/PDA authority) or an explicit external
|
||||||
|
// authority account (one entry), so a rotated authority can act.
|
||||||
|
let authority = authority_accounts.first().unwrap_or(&definition_account);
|
||||||
assert!(
|
assert!(
|
||||||
definition_account.is_authorized,
|
authority.is_authorized,
|
||||||
"Mint authority must authorize the transaction"
|
"Mint authority must authorize the transaction"
|
||||||
);
|
);
|
||||||
let signer: [u8; 32] = definition_account
|
let signer: [u8; 32] = authority
|
||||||
.account_id
|
.account_id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.try_into()
|
.try_into()
|
||||||
@ -56,5 +59,11 @@ pub fn set_authority(
|
|||||||
let mut definition_post = definition_account.account;
|
let mut definition_post = definition_account.account;
|
||||||
definition_post.data = Data::from(&definition);
|
definition_post.data = Data::from(&definition);
|
||||||
|
|
||||||
vec![AccountPostState::new(definition_post)]
|
// Post-states match pre-state order/count: [definition, ...authority_accounts].
|
||||||
|
let mut post_states = Vec::with_capacity(authority_accounts.len().saturating_add(1));
|
||||||
|
post_states.push(AccountPostState::new(definition_post));
|
||||||
|
for authority in authority_accounts {
|
||||||
|
post_states.push(AccountPostState::new(authority.account));
|
||||||
|
}
|
||||||
|
post_states
|
||||||
}
|
}
|
||||||
|
|||||||
@ -911,6 +911,7 @@ fn test_mint_not_valid_holding_account() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -924,6 +925,7 @@ fn test_mint_not_valid_definition_account() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -939,6 +941,7 @@ fn test_mint_missing_authorization() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -952,6 +955,7 @@ fn test_mint_rejects_foreign_owned_definition() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -966,6 +970,7 @@ fn test_mint_mismatched_token_definition() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -978,6 +983,7 @@ fn test_mint_success() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1003,6 +1009,7 @@ fn test_mint_uninit_holding_success() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1029,6 +1036,7 @@ fn test_mint_total_supply_overflow() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_overflow(),
|
BalanceForTests::mint_overflow(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1042,6 +1050,7 @@ fn test_mint_holding_account_overflow() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_overflow(),
|
BalanceForTests::mint_overflow(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1055,6 +1064,7 @@ fn test_mint_cannot_mint_unmintable_tokens() {
|
|||||||
definition_account,
|
definition_account,
|
||||||
holding_account,
|
holding_account,
|
||||||
BalanceForTests::mint_success(),
|
BalanceForTests::mint_success(),
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1067,6 +1077,7 @@ fn test_new_definition_with_metadata_success() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1088,6 +1099,42 @@ fn test_new_definition_with_metadata_success() {
|
|||||||
assert_eq!(metadata_post.required_claim(), Some(Claim::Authorized));
|
assert_eq!(metadata_post.required_claim(), Some(Claim::Authorized));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Comment #2: a metadata-backed fungible created with `mint_authority: Some(..)`
|
||||||
|
/// carries a real, non-renounced authority and is therefore mintable — no longer
|
||||||
|
/// force-fixed-supply the way the hardcoded `Authority::renounced()` made it.
|
||||||
|
#[test]
|
||||||
|
fn test_metadata_fungible_with_authority_is_mintable() {
|
||||||
|
let definition_account = AccountForTests::definition_account_uninit_auth();
|
||||||
|
let holding_account = AccountForTests::holding_account_uninit_auth();
|
||||||
|
let metadata_account = AccountForTests::metadata_account_uninit_auth();
|
||||||
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
|
name: String::from("test"),
|
||||||
|
total_supply: 15u128,
|
||||||
|
mint_authority: Some(AccountId::new([15_u8; 32])),
|
||||||
|
};
|
||||||
|
let metadata = NewTokenMetadata {
|
||||||
|
standard: MetadataStandard::Simple,
|
||||||
|
uri: "test_uri".to_string(),
|
||||||
|
creators: "test_creators".to_string(),
|
||||||
|
};
|
||||||
|
let post_states = new_definition_with_metadata(
|
||||||
|
definition_account,
|
||||||
|
holding_account,
|
||||||
|
metadata_account,
|
||||||
|
new_definition,
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
let [definition_post, _holding_post, _metadata_post] = post_states.try_into().unwrap();
|
||||||
|
|
||||||
|
// The stored authority must be the requested key, NOT renounced.
|
||||||
|
let def = TokenDefinition::try_from(&definition_post.account().data).unwrap();
|
||||||
|
let stored = match def {
|
||||||
|
TokenDefinition::Fungible { authority, .. } => authority.authority(),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
assert_eq!(stored, Some([15_u8; 32]));
|
||||||
|
}
|
||||||
|
|
||||||
#[should_panic(expected = "Definition target account must be authorized")]
|
#[should_panic(expected = "Definition target account must be authorized")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_call_new_definition_metadata_requires_authorized_definition() {
|
fn test_call_new_definition_metadata_requires_authorized_definition() {
|
||||||
@ -1097,6 +1144,7 @@ fn test_call_new_definition_metadata_requires_authorized_definition() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1121,6 +1169,7 @@ fn test_call_new_definition_metadata_requires_authorized_holding() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1149,6 +1198,7 @@ fn test_call_new_definition_metadata_requires_authorized_metadata() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1181,6 +1231,7 @@ fn test_call_new_definition_metadata_with_init_definition() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1213,6 +1264,7 @@ fn test_call_new_definition_metadata_with_init_metadata() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1245,6 +1297,7 @@ fn test_call_new_definition_metadata_with_init_holding() {
|
|||||||
let new_definition = NewTokenDefinition::Fungible {
|
let new_definition = NewTokenDefinition::Fungible {
|
||||||
name: String::from("test"),
|
name: String::from("test"),
|
||||||
total_supply: 15u128,
|
total_supply: 15u128,
|
||||||
|
mint_authority: None,
|
||||||
};
|
};
|
||||||
let metadata = NewTokenMetadata {
|
let metadata = NewTokenMetadata {
|
||||||
standard: MetadataStandard::Simple,
|
standard: MetadataStandard::Simple,
|
||||||
@ -1418,6 +1471,7 @@ mod authority_tests {
|
|||||||
def_with_authority(),
|
def_with_authority(),
|
||||||
holding_account(),
|
holding_account(),
|
||||||
50_000,
|
50_000,
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
let [def_post, holding_post] = post_states.try_into().unwrap();
|
let [def_post, holding_post] = post_states.try_into().unwrap();
|
||||||
@ -1448,6 +1502,7 @@ mod authority_tests {
|
|||||||
def_with_authority_revoked(),
|
def_with_authority_revoked(),
|
||||||
holding_account(),
|
holding_account(),
|
||||||
50_000,
|
50_000,
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1457,7 +1512,7 @@ mod authority_tests {
|
|||||||
fn mint_without_is_authorized_fails() {
|
fn mint_without_is_authorized_fails() {
|
||||||
let mut def = def_with_authority();
|
let mut def = def_with_authority();
|
||||||
def.is_authorized = false;
|
def.is_authorized = false;
|
||||||
let _ = mint(def, holding_account(), 50_000, TOKEN_PROGRAM_ID);
|
let _ = mint(def, holding_account(), 50_000, vec![], TOKEN_PROGRAM_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1467,6 +1522,7 @@ mod authority_tests {
|
|||||||
def_wrong_authority(),
|
def_wrong_authority(),
|
||||||
holding_account(),
|
holding_account(),
|
||||||
50_000,
|
50_000,
|
||||||
|
vec![],
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1474,13 +1530,17 @@ mod authority_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "New mint authority must be a valid non-zero account ID")]
|
#[should_panic(expected = "New mint authority must be a valid non-zero account ID")]
|
||||||
fn set_authority_rejects_zero_new_authority() {
|
fn set_authority_rejects_zero_new_authority() {
|
||||||
let _ = set_authority(def_with_authority(), Some(AccountId::new([0u8; 32])));
|
let _ = set_authority(
|
||||||
|
def_with_authority(),
|
||||||
|
Some(AccountId::new([0u8; 32])),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_authority_rotates_to_new_key() {
|
fn set_authority_rotates_to_new_key() {
|
||||||
let new_key = AccountId::new([7_u8; 32]);
|
let new_key = AccountId::new([7_u8; 32]);
|
||||||
let post_states = set_authority(def_with_authority(), Some(new_key));
|
let post_states = set_authority(def_with_authority(), Some(new_key), vec![]);
|
||||||
let [def_post] = post_states.try_into().unwrap();
|
let [def_post] = post_states.try_into().unwrap();
|
||||||
|
|
||||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||||
@ -1493,7 +1553,7 @@ mod authority_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_authority_revokes_permanently() {
|
fn set_authority_revokes_permanently() {
|
||||||
let post_states = set_authority(def_with_authority(), None);
|
let post_states = set_authority(def_with_authority(), None, vec![]);
|
||||||
let [def_post] = post_states.try_into().unwrap();
|
let [def_post] = post_states.try_into().unwrap();
|
||||||
|
|
||||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||||
@ -1510,6 +1570,7 @@ mod authority_tests {
|
|||||||
let _ = set_authority(
|
let _ = set_authority(
|
||||||
def_with_authority_revoked(),
|
def_with_authority_revoked(),
|
||||||
Some(AccountId::new([7_u8; 32])),
|
Some(AccountId::new([7_u8; 32])),
|
||||||
|
vec![],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1518,19 +1579,23 @@ mod authority_tests {
|
|||||||
fn set_authority_without_is_authorized_fails() {
|
fn set_authority_without_is_authorized_fails() {
|
||||||
let mut def = def_with_authority();
|
let mut def = def_with_authority();
|
||||||
def.is_authorized = false;
|
def.is_authorized = false;
|
||||||
let _ = set_authority(def, Some(AccountId::new([7_u8; 32])));
|
let _ = set_authority(def, Some(AccountId::new([7_u8; 32])), vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "SetAuthority failed")]
|
#[should_panic(expected = "SetAuthority failed")]
|
||||||
fn set_authority_wrong_signer_fails() {
|
fn set_authority_wrong_signer_fails() {
|
||||||
let _ = set_authority(def_wrong_authority(), Some(AccountId::new([7_u8; 32])));
|
let _ = set_authority(
|
||||||
|
def_wrong_authority(),
|
||||||
|
Some(AccountId::new([7_u8; 32])),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_authority_rotate_then_old_cannot_mint() {
|
fn set_authority_rotate_then_old_cannot_mint() {
|
||||||
let new_key = AccountId::new([7_u8; 32]);
|
let new_key = AccountId::new([7_u8; 32]);
|
||||||
let post_states = set_authority(def_with_authority(), Some(new_key));
|
let post_states = set_authority(def_with_authority(), Some(new_key), vec![]);
|
||||||
let [def_post] = post_states.try_into().unwrap();
|
let [def_post] = post_states.try_into().unwrap();
|
||||||
|
|
||||||
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
|
||||||
@ -1542,4 +1607,82 @@ mod authority_tests {
|
|||||||
assert_eq!(auth, Some([7_u8; 32]));
|
assert_eq!(auth, Some([7_u8; 32]));
|
||||||
assert_ne!(auth, Some(AUTHORITY));
|
assert_ne!(auth, Some(AUTHORITY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Authority signer for the rotated key B ([7;32]), authorized.
|
||||||
|
fn new_authority_signer() -> AccountWithMetadata {
|
||||||
|
AccountWithMetadata {
|
||||||
|
account: Account::default(),
|
||||||
|
is_authorized: true,
|
||||||
|
account_id: AccountId::new([7_u8; 32]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RFP-001 end-to-end (comment #1): after rotating authority A -> B, the new
|
||||||
|
/// authority B can actually mint by presenting itself in `authority_accounts`.
|
||||||
|
#[test]
|
||||||
|
fn rotated_authority_can_mint() {
|
||||||
|
// Rotate A ([15;32]) -> B ([7;32]), signed by A via self-authority.
|
||||||
|
let rotate_post = set_authority(
|
||||||
|
def_with_authority(),
|
||||||
|
Some(AccountId::new([7_u8; 32])),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
let [def_post] = rotate_post.try_into().unwrap();
|
||||||
|
|
||||||
|
// Rebuild the definition carrying the rotated authority, re-authorized.
|
||||||
|
let mut rotated_def = def_with_authority();
|
||||||
|
rotated_def.account = def_post.account().clone();
|
||||||
|
|
||||||
|
// B mints by presenting itself as the external authority.
|
||||||
|
let mint_post = mint(
|
||||||
|
rotated_def,
|
||||||
|
holding_account(),
|
||||||
|
10_000,
|
||||||
|
vec![new_authority_signer()],
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
);
|
||||||
|
let [def_after, holding_after, _auth] = mint_post.try_into().unwrap();
|
||||||
|
let minted = TokenDefinition::try_from(&def_after.account().data).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
minted,
|
||||||
|
TokenDefinition::Fungible {
|
||||||
|
total_supply: 110_000,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
));
|
||||||
|
let holding = TokenHolding::try_from(&holding_after.account().data).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
holding,
|
||||||
|
TokenHolding::Fungible {
|
||||||
|
balance: 11_000,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Comment #1 negative: after rotation to B, the OLD authority A can no
|
||||||
|
/// longer mint. Here A attempts self-authority (empty `authority_accounts`),
|
||||||
|
/// but the definition's own id no longer matches the stored authority B.
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Mint authority check failed")]
|
||||||
|
fn rotated_authority_old_key_cannot_mint() {
|
||||||
|
let rotate_post = set_authority(
|
||||||
|
def_with_authority(),
|
||||||
|
Some(AccountId::new([7_u8; 32])),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
let [def_post] = rotate_post.try_into().unwrap();
|
||||||
|
|
||||||
|
let mut rotated_def = def_with_authority();
|
||||||
|
rotated_def.account = def_post.account().clone();
|
||||||
|
|
||||||
|
// A ([15;32]) is no longer the authority; self-authority must fail.
|
||||||
|
let _ = mint(
|
||||||
|
rotated_def,
|
||||||
|
holding_account(),
|
||||||
|
10_000,
|
||||||
|
vec![],
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,22 @@ fi
|
|||||||
SPEL="${SPEL:-$HOME/rebase-lez/spel/target/release/spel}"
|
SPEL="${SPEL:-$HOME/rebase-lez/spel/target/release/spel}"
|
||||||
LEZ_PROGRAMS="${LEZ_PROGRAMS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
LEZ_PROGRAMS="${LEZ_PROGRAMS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
IDL="${IDL:-$LEZ_PROGRAMS/artifacts/token-idl.json}"
|
IDL="${IDL:-$LEZ_PROGRAMS/artifacts/token-idl.json}"
|
||||||
TOKEN_BIN="${TOKEN_BIN:-$LEZ_PROGRAMS/target/riscv-guest/token-methods/token-guest/riscv32im-risc0-zkvm-elf/release/token.bin}"
|
# Resolve the token guest binary from either build layout, in priority order:
|
||||||
|
# 1. `cargo risczero build --manifest-path programs/token/methods/guest/Cargo.toml`
|
||||||
|
# -> programs/token/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/token.bin
|
||||||
|
# (the build command documented in the README)
|
||||||
|
# 2. workspace build (`cargo build` / `cargo test`)
|
||||||
|
# -> target/riscv-guest/token-methods/token-guest/riscv32im-risc0-zkvm-elf/release/token.bin
|
||||||
|
# An explicit TOKEN_BIN env var always takes precedence.
|
||||||
|
_risc0_token_bin="$LEZ_PROGRAMS/programs/token/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/token.bin"
|
||||||
|
_workspace_token_bin="$LEZ_PROGRAMS/target/riscv-guest/token-methods/token-guest/riscv32im-risc0-zkvm-elf/release/token.bin"
|
||||||
|
if [ -z "${TOKEN_BIN:-}" ]; then
|
||||||
|
if [ -f "$_risc0_token_bin" ]; then
|
||||||
|
TOKEN_BIN="$_risc0_token_bin"
|
||||||
|
else
|
||||||
|
TOKEN_BIN="$_workspace_token_bin"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
DEMO_DIR="${DEMO_DIR:-$(pwd)}"
|
DEMO_DIR="${DEMO_DIR:-$(pwd)}"
|
||||||
WALLET_DIR="${WALLET_DIR:-$DEMO_DIR/.scaffold/wallet}"
|
WALLET_DIR="${WALLET_DIR:-$DEMO_DIR/.scaffold/wallet}"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user