mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-03 13:39:38 +00:00
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:
parent
686a7d066a
commit
aee5372f2a
@ -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 |
|
||||
|
||||
@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user