diff --git a/docs/LP-0013-README.md b/docs/LP-0013-README.md index b11a848..44cabb2 100644 --- a/docs/LP-0013-README.md +++ b/docs/LP-0013-README.md @@ -15,7 +15,7 @@ The `lez-authority` crate provides a reusable, program-agnostic authority librar ## Architecture -### Authority Model +### Authority Model `mint_authority: Option<[u8; 32]>` is added to `TokenDefinition::Fungible`: - `Some(key)` — the key holder can mint and rotate/revoke diff --git a/programs/token/methods/guest/src/bin/token.rs b/programs/token/methods/guest/src/bin/token.rs index a5971a8..935f220 100644 --- a/programs/token/methods/guest/src/bin/token.rs +++ b/programs/token/methods/guest/src/bin/token.rs @@ -148,6 +148,7 @@ mod token { /// The definition account must be authorized as the current mint authority. #[instruction] pub fn set_authority( + ctx: ProgramContext, definition_account: AccountWithMetadata, authority_accounts: Vec, new_authority: Option, @@ -157,6 +158,7 @@ mod token { definition_account, new_authority, authority_accounts, + ctx.self_program_id, ), vec![], )) diff --git a/programs/token/src/set_authority.rs b/programs/token/src/set_authority.rs index d0fabdd..2f6e9e3 100644 --- a/programs/token/src/set_authority.rs +++ b/programs/token/src/set_authority.rs @@ -1,7 +1,7 @@ use lez_authority::Ownable; use nssa_core::{ account::{AccountId, AccountWithMetadata, Data}, - program::AccountPostState, + program::{AccountPostState, ProgramId}, }; use token_core::TokenDefinition; @@ -9,7 +9,13 @@ pub fn set_authority( definition_account: AccountWithMetadata, new_authority: Option, authority_accounts: Vec, + token_program_id: ProgramId, ) -> Vec { + assert_eq!( + definition_account.account.program_owner, token_program_id, + "Token definition must be owned by token program" + ); + let mut definition = TokenDefinition::try_from(&definition_account.account.data) .expect("Token Definition account must be valid"); diff --git a/programs/token/src/tests.rs b/programs/token/src/tests.rs index 75317e4..c8f9080 100644 --- a/programs/token/src/tests.rs +++ b/programs/token/src/tests.rs @@ -18,6 +18,7 @@ use crate::{ mint::mint, new_definition::{new_definition_with_metadata, new_fungible_definition}, print_nft::print_nft, + set_authority::set_authority, transfer::transfer, }; @@ -960,6 +961,20 @@ fn test_mint_rejects_foreign_owned_definition() { ); } +#[test] +#[should_panic(expected = "Token definition must be owned by token program")] +fn test_set_authority_rejects_foreign_owned_definition() { + // A foreign-owned account carrying token-shaped data must not be able to + // rotate or revoke its authority through the token program. + let definition_account = AccountForTests::definition_account_foreign_owner(); + let _post_states = set_authority( + definition_account, + Some(AccountId::new([7_u8; 32])), + vec![], + TOKEN_PROGRAM_ID, + ); +} + #[test] #[should_panic(expected = "Mismatch Token Definition and Token Holding")] fn test_mint_mismatched_token_definition() { @@ -1534,13 +1549,19 @@ mod authority_tests { def_with_authority(), Some(AccountId::new([0u8; 32])), vec![], + TOKEN_PROGRAM_ID, ); } #[test] fn set_authority_rotates_to_new_key() { let new_key = AccountId::new([7_u8; 32]); - let post_states = set_authority(def_with_authority(), Some(new_key), vec![]); + let post_states = set_authority( + def_with_authority(), + Some(new_key), + vec![], + TOKEN_PROGRAM_ID, + ); let [def_post] = post_states.try_into().unwrap(); let def = TokenDefinition::try_from(&def_post.account().data).unwrap(); @@ -1553,7 +1574,7 @@ mod authority_tests { #[test] fn set_authority_revokes_permanently() { - let post_states = set_authority(def_with_authority(), None, vec![]); + let post_states = set_authority(def_with_authority(), None, vec![], TOKEN_PROGRAM_ID); let [def_post] = post_states.try_into().unwrap(); let def = TokenDefinition::try_from(&def_post.account().data).unwrap(); @@ -1571,6 +1592,7 @@ mod authority_tests { def_with_authority_revoked(), Some(AccountId::new([7_u8; 32])), vec![], + TOKEN_PROGRAM_ID, ); } @@ -1579,7 +1601,12 @@ mod authority_tests { fn set_authority_without_is_authorized_fails() { let mut def = def_with_authority(); def.is_authorized = false; - let _ = set_authority(def, Some(AccountId::new([7_u8; 32])), vec![]); + let _ = set_authority( + def, + Some(AccountId::new([7_u8; 32])), + vec![], + TOKEN_PROGRAM_ID, + ); } #[test] @@ -1589,13 +1616,19 @@ mod authority_tests { def_wrong_authority(), Some(AccountId::new([7_u8; 32])), vec![], + TOKEN_PROGRAM_ID, ); } #[test] fn set_authority_rotate_then_old_cannot_mint() { let new_key = AccountId::new([7_u8; 32]); - let post_states = set_authority(def_with_authority(), Some(new_key), vec![]); + let post_states = set_authority( + def_with_authority(), + Some(new_key), + vec![], + TOKEN_PROGRAM_ID, + ); let [def_post] = post_states.try_into().unwrap(); let def = TokenDefinition::try_from(&def_post.account().data).unwrap(); @@ -1626,6 +1659,7 @@ mod authority_tests { def_with_authority(), Some(AccountId::new([7_u8; 32])), vec![], + TOKEN_PROGRAM_ID, ); let [def_post] = rotate_post.try_into().unwrap(); @@ -1670,6 +1704,7 @@ mod authority_tests { def_with_authority(), Some(AccountId::new([7_u8; 32])), vec![], + TOKEN_PROGRAM_ID, ); let [def_post] = rotate_post.try_into().unwrap(); diff --git a/scripts/demo-full-flow.sh b/scripts/demo-full-flow.sh index ef7a3a7..c780a41 100755 --- a/scripts/demo-full-flow.sh +++ b/scripts/demo-full-flow.sh @@ -18,7 +18,7 @@ # 1. Start a local LEZ sequencer # 2. Fund the wallet # 3. Create token accounts -# 4. Submit NewFungibleDefinitionWithAuthority transaction +# 4. Submit NewFungibleDefinition transaction (with mint authority) # 5. Submit Mint transaction (authority-gated) # 6. Submit SetAuthority (revoke) transaction # 7. Run unit tests to verify authority logic (60 tests) @@ -101,11 +101,11 @@ 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 \ + -- new-fungible-definition \ --definition-target-account "$DEF_ID" \ --holding-target-account "$SUPPLY_ID" \ --name "DemoCoin" \ - --initial-supply 1000000 \ + --total-supply 1000000 \ --mint-authority "$DEF_ID_HEX" echo " Token 'DemoCoin' submitted. Initial supply: 1,000,000" sleep 2 @@ -115,7 +115,6 @@ 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" @@ -126,7 +125,6 @@ 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" sleep 2 @@ -139,7 +137,7 @@ echo "" echo "================================================================" echo " LP-0013 Demo Complete" echo " Summary:" -echo " [1/4] NewFungibleDefinitionWithAuthority → supply=1,000,000" +echo " [1/4] NewFungibleDefinition (with authority) → supply=1,000,000" echo " [2/4] Mint 500,000 → supply=1,500,000" echo " [3/4] SetAuthority (revoke) → supply fixed" echo " [4/4] Unit tests passing → all authority cases verified"