fix: enforce mint authority key validation in mint and set_authority

- mint.rs: validate caller account_id matches stored mint_authority key
- set_authority.rs: validate caller matches mint_authority before rotation/revoke
- tests.rs: align AUTHORITY constant and fixtures to match account_id [15; 32]
- demo-full-flow.sh: fix --public flag, remove || true from spel commands,
  update test count to 60
This commit is contained in:
bristinWild 2026-06-02 02:03:35 +05:30
parent 7e788f4215
commit b561f91db2
4 changed files with 29 additions and 21 deletions

View File

@ -24,10 +24,16 @@ pub fn mint(
// LP-0013: enforce mint authority — minting is only allowed if mint_authority is Some. // LP-0013: enforce mint authority — minting is only allowed if mint_authority is Some.
if let TokenDefinition::Fungible { mint_authority, .. } = &definition { if let TokenDefinition::Fungible { mint_authority, .. } = &definition {
assert!( match mint_authority {
mint_authority.is_some(), None => panic!("Mint authority has been revoked; this token has a fixed supply"),
"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"
);
}
}
} }
let mut holding = if user_holding_account.account == Account::default() { let mut holding = if user_holding_account.account == Account::default() {
TokenHolding::zeroized_from_definition(definition_account.account_id, &definition) TokenHolding::zeroized_from_definition(definition_account.account_id, &definition)

View File

@ -9,9 +9,6 @@ pub fn set_authority(
definition_account: AccountWithMetadata, definition_account: AccountWithMetadata,
new_authority: Option<[u8; 32]>, new_authority: Option<[u8; 32]>,
) -> Vec<AccountPostState> { ) -> Vec<AccountPostState> {
// The definition account must be authorized — this means the transaction
// signer controls the definition account, which is how mint authority
// is enforced in LEZ (account-level authorization).
assert!( assert!(
definition_account.is_authorized, definition_account.is_authorized,
"Definition account authorization is missing; only the mint authority can call SetAuthority" "Definition account authorization is missing; only the mint authority can call SetAuthority"
@ -26,8 +23,13 @@ pub fn set_authority(
None => { None => {
panic!("Mint authority already revoked; supply is permanently fixed"); panic!("Mint authority already revoked; supply is permanently fixed");
} }
Some(_) => { Some(authority_key) => {
// Rotate to new authority, or revoke by setting to None // 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; *mint_authority = new_authority;
} }
} }
@ -41,4 +43,4 @@ pub fn set_authority(
definition_post.data = Data::from(&definition); definition_post.data = Data::from(&definition);
vec![AccountPostState::new(definition_post)] vec![AccountPostState::new(definition_post)]
} }

View File

@ -42,7 +42,7 @@ impl AccountForTests {
name: String::from("test"), name: String::from("test"),
total_supply: BalanceForTests::init_supply(), total_supply: BalanceForTests::init_supply(),
metadata_id: None, metadata_id: None,
mint_authority: Some([1_u8; 32]), mint_authority: Some([15_u8; 32]),
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -160,7 +160,7 @@ impl AccountForTests {
name: String::from("test"), name: String::from("test"),
total_supply: BalanceForTests::init_supply_burned(), total_supply: BalanceForTests::init_supply_burned(),
metadata_id: None, metadata_id: None,
mint_authority: Some([1_u8; 32]), mint_authority: Some([15_u8; 32]),
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -242,7 +242,7 @@ impl AccountForTests {
name: String::from("test"), name: String::from("test"),
total_supply: BalanceForTests::init_supply_mint(), total_supply: BalanceForTests::init_supply_mint(),
metadata_id: None, metadata_id: None,
mint_authority: Some([1_u8; 32]), mint_authority: Some([15_u8; 32]),
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1325,7 +1325,7 @@ mod authority_tests {
use super::*; use super::*;
use crate::{mint::mint, set_authority::set_authority}; use crate::{mint::mint, set_authority::set_authority};
const AUTHORITY: [u8; 32] = [9_u8; 32]; const AUTHORITY: [u8; 32] = [15_u8; 32];
const TOKEN_PROGRAM_ID: [u32; 8] = [5_u32; 8]; const TOKEN_PROGRAM_ID: [u32; 8] = [5_u32; 8];
fn def_with_authority() -> AccountWithMetadata { fn def_with_authority() -> AccountWithMetadata {

View File

@ -26,7 +26,7 @@
# 4. Submit NewFungibleDefinitionWithAuthority transaction # 4. Submit NewFungibleDefinitionWithAuthority transaction
# 5. Submit Mint transaction # 5. Submit Mint transaction
# 6. Submit SetAuthority (revoke) transaction # 6. Submit SetAuthority (revoke) transaction
# 7. Run unit tests to verify authority logic (49 tests) # 7. Run unit tests to verify authority logic (60 tests)
set -euo pipefail set -euo pipefail
@ -66,11 +66,11 @@ lgs wallet topup 2>&1 | grep -E "complete|funded|Address" || true
echo " Wallet funded." echo " Wallet funded."
echo "[3/7] Creating token accounts..." echo "[3/7] Creating token accounts..."
DEF_RESULT=$(lgs wallet -- account new public 2>&1) DEF_RESULT=$(lgs wallet -- account new --public 2>&1)
DEF_ID=$(echo "$DEF_RESULT" | grep -oE '[0-9a-f]{64}' | head -1) DEF_ID=$(echo "$DEF_RESULT" | grep -oE '[0-9a-f]{64}' | head -1)
SUPPLY_RESULT=$(lgs wallet -- account new public 2>&1) SUPPLY_RESULT=$(lgs wallet -- account new --public 2>&1)
SUPPLY_ID=$(echo "$SUPPLY_RESULT" | grep -oE '[0-9a-f]{64}' | head -1) SUPPLY_ID=$(echo "$SUPPLY_RESULT" | grep -oE '[0-9a-f]{64}' | head -1)
RECIPIENT_RESULT=$(lgs wallet -- account new public 2>&1) RECIPIENT_RESULT=$(lgs wallet -- account new --public 2>&1)
RECIPIENT_ID=$(echo "$RECIPIENT_RESULT" | grep -oE '[0-9a-f]{64}' | head -1) RECIPIENT_ID=$(echo "$RECIPIENT_RESULT" | grep -oE '[0-9a-f]{64}' | head -1)
echo " Definition account: $DEF_ID" echo " Definition account: $DEF_ID"
echo " Supply account: $SUPPLY_ID" echo " Supply account: $SUPPLY_ID"
@ -84,7 +84,7 @@ ${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
--holding-target-account "$SUPPLY_ID" \ --holding-target-account "$SUPPLY_ID" \
--name "DemoCoin" \ --name "DemoCoin" \
--initial-supply 1000000 \ --initial-supply 1000000 \
--mint-authority "$DEF_ID" 2>&1 || true --mint-authority "$DEF_ID"
echo " Token 'DemoCoin' submitted. Initial supply: 1,000,000" echo " Token 'DemoCoin' submitted. Initial supply: 1,000,000"
sleep 2 sleep 2
@ -95,7 +95,7 @@ ${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- mint \ -- mint \
--definition-account "$DEF_ID" \ --definition-account "$DEF_ID" \
--user-holding-account "$RECIPIENT_ID" \ --user-holding-account "$RECIPIENT_ID" \
--amount-to-mint 500000 2>&1 || true --amount-to-mint 500000
echo " Mint transaction submitted. New total supply: 1,500,000" echo " Mint transaction submitted. New total supply: 1,500,000"
sleep 2 sleep 2
@ -105,7 +105,7 @@ NSSA_WALLET_HOME_DIR="$WALLET_DIR" \
${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \ ${TIMEOUT:+$TIMEOUT 30} "$SPEL" --idl "$IDL" --program "$TOKEN_BIN" \
-- set-authority \ -- set-authority \
--definition-account "$DEF_ID" \ --definition-account "$DEF_ID" \
--new-authority none 2>&1 || true --new-authority none
echo " Authority revoked. Supply permanently fixed at 1,500,000" echo " Authority revoked. Supply permanently fixed at 1,500,000"
sleep 2 sleep 2