fix(lp-0013): address review feedback — docs alignment and missing rotation test

- Add integration test `token_rotate_authority_then_new_authority_can_mint`:
  create with self-authority, rotate to external key, verify new authority
  mints as rest account, verify old authority is rejected (RFP-001 end-to-end)
- Fix README error table: 'must sign' -> 'must authorize' (matches mint.rs:36)
- Fix guest doc comments for mint/set_authority to describe the 0-or-1
  external authority model correctly
- Fix example scripts: new-fungible-definition-with-authority -> new-fungible-definition,
  --initial-supply -> --total-supply (align to token-idl.json and demo-full-flow.sh)
This commit is contained in:
bristinWild 2026-06-23 04:05:44 +05:30
parent 686a7d066a
commit aee5372f2a
5 changed files with 148 additions and 7 deletions

View File

@ -39,7 +39,7 @@ The `lez-authority` crate provides a reusable, program-agnostic authority librar
|---|---|
| Mint when authority revoked | Mint authority check failed: Revoked |
| Mint by non-authority signer | Mint authority check failed: Unauthorized |
| Mint/SetAuthority without signed authority | Mint authority must sign the transaction |
| Mint/SetAuthority without signed authority | Mint authority must authorize the transaction |
| SetAuthority on already-revoked | SetAuthority failed: AlreadyRevoked |
| SetAuthority by wrong signer | SetAuthority failed: Unauthorized |
| Create/rotate with all-zero authority | Mint authority must be a valid non-zero account ID |

View File

@ -1069,3 +1069,144 @@ fn token_set_authority_revoke() {
}
);
}
/// Integration test for RFP-001 authority rotation flow:
/// 1. Create a token where `Ids::token_definition()` is the initial mint authority (self-authority).
/// 2. Rotate the mint authority to `Ids::authority()` (an external key).
/// 3. Verify that the new external authority can mint by presenting itself as a rest account.
/// 4. Verify that the OLD authority (def key) can no longer mint after rotation.
#[test]
fn token_rotate_authority_then_new_authority_can_mint() {
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
deploy_token(&mut state);
let authority_key: [u8; 32] = Ids::authority()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes");
// Step 1: Create token with self-authority (def account is initial mint authority).
let instruction = token_core::Instruction::NewFungibleDefinition {
name: String::from("RotCoin"),
total_supply: 1_000_000_u128,
mint_authority: Some(AccountId::new(
Ids::token_definition()
.as_ref()
.try_into()
.expect("AccountId is always 32 bytes"),
)),
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::holder()],
vec![Nonce(0), Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(
&message,
&[&Keys::def_key(), &Keys::holder_key()],
);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
// Step 2: Rotate mint authority from def_key to Ids::authority() (external key).
// Self-authority path: no rest accounts; def_key signs.
let instruction = token_core::Instruction::SetAuthority {
new_authority: Some(AccountId::new(authority_key)),
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition()],
vec![Nonce(1)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let tx = PublicTransaction::new(message, witness_set);
state.transition_from_public_transaction(&tx, 0, 0).unwrap();
// Verify the authority slot now holds Ids::authority().
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("RotCoin"),
total_supply: 1_000_000_u128,
metadata_id: None,
authority: token_core::Authority::new(authority_key),
}),
nonce: Nonce(2),
}
);
// Seed the external authority account and the holder so they exist in state.
state.force_insert_account(Ids::authority(), Accounts::authority_init());
state.force_insert_account(Ids::holder(), Accounts::holder_init());
// Step 3: New external authority mints by presenting itself as a rest account.
// mint accounts: [definition_account, holder_account, ...authority_accounts]
let instruction = token_core::Instruction::Mint {
amount_to_mint: 500_000_u128,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::holder(), Ids::authority()],
vec![Nonce(0)],
instruction,
)
.unwrap();
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();
// Verify total_supply increased and holder balance reflects the mint.
assert_eq!(
state.get_account_by_id(Ids::token_definition()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenDefinition::Fungible {
name: String::from("RotCoin"),
total_supply: 1_500_000_u128,
metadata_id: None,
authority: token_core::Authority::new(authority_key),
}),
nonce: Nonce(2),
}
);
assert_eq!(
state.get_account_by_id(Ids::holder()),
Account {
program_owner: Ids::token_program(),
balance: 0_u128,
data: Data::from(&TokenHolding::Fungible {
definition_id: Ids::token_definition(),
balance: 1_500_000_u128,
}),
nonce: Nonce(0),
}
);
// Step 4: OLD authority (def_key self-authority path) must be rejected after rotation.
let instruction = token_core::Instruction::Mint {
amount_to_mint: 1_u128,
};
let message = public_transaction::Message::try_new(
Ids::token_program(),
vec![Ids::token_definition(), Ids::holder()],
vec![Nonce(0)],
instruction,
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx, 0, 0);
assert!(
result.is_err(),
"Old authority must be rejected after rotation"
);
}

View File

@ -120,7 +120,7 @@ mod token {
}
/// Mint new tokens to the holder's account.
/// The definition account must be authorized as the current mint authority.
/// The current mint authority must authorize the transaction: either the definition account itself when `authority_accounts` is empty (self/PDA authority), or an external authority account passed as the first rest account after rotation.
/// Fresh public holders must be explicitly authorized in the same transaction.
#[instruction]
pub fn mint(
@ -145,7 +145,7 @@ mod token {
/// Rotate or renounce the mint authority for a fungible token definition.
/// Pass `new_authority: None` to permanently renounce minting (fixed supply).
/// The definition account must be authorized as the current mint authority.
/// The current mint authority must authorize the transaction: either the definition account itself when `authority_accounts` is empty (self/PDA authority), or an external authority account passed as the first rest account after rotation.
#[instruction]
pub fn set_authority(
ctx: ProgramContext,

View File

@ -53,11 +53,11 @@ echo " Holding: $HOLD_ID"
echo "[3/4] Creating token with mint authority..."
NSSA_WALLET_HOME_DIR="$WALLET_DIR" \
"$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- new-fungible-definition-with-authority \
-- new-fungible-definition \
--definition-target-account "$DEF_ID" \
--holding-target-account "$HOLD_ID" \
--name "FixedCoin" \
--initial-supply 1000000 \
--total-supply 1000000 \
--mint-authority "$DEF_ID_HEX"
echo " Token 'FixedCoin' created. Initial supply: 1,000,000"
sleep 2

View File

@ -56,11 +56,11 @@ echo " New authority (rotation target): $NEW_AUTH_ID"
echo "[3/5] Creating token with mint authority (the definition account)..."
NSSA_WALLET_HOME_DIR="$WALLET_DIR" \
"$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- new-fungible-definition-with-authority \
-- new-fungible-definition \
--definition-target-account "$DEF_ID" \
--holding-target-account "$HOLD_ID" \
--name "VarCoin" \
--initial-supply 100000 \
--total-supply 100000 \
--mint-authority "$DEF_ID_HEX"
echo " Token 'VarCoin' created. Initial supply: 100,000"
sleep 2