From 8ff1f3d0f78fd017a25e8b0cece578af34a55687 Mon Sep 17 00:00:00 2001 From: febo Date: Sun, 17 May 2026 10:32:57 +0100 Subject: [PATCH 1/3] Add immutable check --- program/src/processor/set_authority.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/program/src/processor/set_authority.rs b/program/src/processor/set_authority.rs index db223e6..1c64387 100644 --- a/program/src/processor/set_authority.rs +++ b/program/src/processor/set_authority.rs @@ -1,6 +1,7 @@ use pinocchio::{account::AccountView, error::ProgramError, Address, ProgramResult}; use crate::{ + error::ProgramMetadataError, processor::validate_authority, state::{buffer::Buffer, header::Header, AccountDiscriminator, Zeroable}, }; @@ -61,6 +62,10 @@ pub fn set_authority(accounts: &mut [AccountView], instruction_data: &[u8]) -> P return Err(ProgramError::InvalidAccountData); } + if !header.mutable() { + return Err(ProgramMetadataError::ImmutableMetadataAccount.into()); + } + validate_authority(header, authority, program, program_data)?; header.authority = if *has_new_authority == 0 { From 45810b8d15b862cff89b4f089779be017cfb5a4b Mon Sep 17 00:00:00 2001 From: febo Date: Sun, 17 May 2026 10:33:09 +0100 Subject: [PATCH 2/3] Add test --- clients/js/test/setAuthority.test.ts | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/clients/js/test/setAuthority.test.ts b/clients/js/test/setAuthority.test.ts index bd1a37f..d0b1717 100644 --- a/clients/js/test/setAuthority.test.ts +++ b/clients/js/test/setAuthority.test.ts @@ -17,6 +17,9 @@ import { fetchMetadata, getAllocateInstruction, getSetAuthorityInstruction, + getSetImmutableInstruction, + isProgramMetadataError, + PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT, } from '../src'; import { createCanonicalMetadata, @@ -392,3 +395,61 @@ test('the authority cannot remove itself on buffer accounts', async t => { const error = await t.throwsAsync(promise); t.true(isSolanaError(error.cause, SOLANA_ERROR__INSTRUCTION_ERROR__INVALID_ARGUMENT)); }); + +test('the authority cannot be changed on immutable metadata accounts', async t => { + // Given the following authorities and deployed program. + const client = createDefaultSolanaClient(); + const [authority, explicitAuthority, anotherAuthority] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + generateKeyPairSigner(), + ]); + const [program, programData] = await createDeployedProgram(client, authority); + + // And the following initialized canonical metadata account. + const [metadata] = await createCanonicalMetadata(client, { + authority, + program, + programData, + seed: 'dummy', + data: getUtf8Encoder().encode('Hello, World!'), + }); + + // And given the explicit authority is set on the metadata account. + const setAuthorityIx = getSetAuthorityInstruction({ + account: metadata, + authority, + program, + programData, + newAuthority: explicitAuthority.address, + }); + + // And the explicit authority sets the metadata account to be immutable. + const setImmutableIx = getSetImmutableInstruction({ + metadata, + authority: explicitAuthority, + program, + programData, + }); + + // When the explicit authority attempts to set another authority on the + // metadata account after setting it to be immutable. + // And given the explicit authority is set on the metadata account. + const setAnotherAuthorityIx = getSetAuthorityInstruction({ + account: metadata, + authority, + program, + programData, + newAuthority: anotherAuthority.address, + }); + + const transactionMessage = pipe( + await createDefaultTransaction(client, authority), + tx => appendTransactionMessageInstructions([setAuthorityIx, setImmutableIx, setAnotherAuthorityIx], tx), + ); + const promise = signAndSendTransaction(client, transactionMessage); + + // Then we expect the transaction to fail. + const error = await t.throwsAsync(promise); + t.true(isProgramMetadataError(error.cause, transactionMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT)); +}); From 25c3d1e74edb1b04876ef40063c5d54d4cfb46c0 Mon Sep 17 00:00:00 2001 From: febo Date: Sun, 17 May 2026 16:59:09 +0100 Subject: [PATCH 3/3] Fix formatting --- clients/js/test/setAuthority.test.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/clients/js/test/setAuthority.test.ts b/clients/js/test/setAuthority.test.ts index d0b1717..dee502f 100644 --- a/clients/js/test/setAuthority.test.ts +++ b/clients/js/test/setAuthority.test.ts @@ -434,7 +434,6 @@ test('the authority cannot be changed on immutable metadata accounts', async t = // When the explicit authority attempts to set another authority on the // metadata account after setting it to be immutable. - // And given the explicit authority is set on the metadata account. const setAnotherAuthorityIx = getSetAuthorityInstruction({ account: metadata, authority, @@ -442,13 +441,11 @@ test('the authority cannot be changed on immutable metadata accounts', async t = programData, newAuthority: anotherAuthority.address, }); - - const transactionMessage = pipe( - await createDefaultTransaction(client, authority), - tx => appendTransactionMessageInstructions([setAuthorityIx, setImmutableIx, setAnotherAuthorityIx], tx), + const transactionMessage = pipe(await createDefaultTransaction(client, authority), tx => + appendTransactionMessageInstructions([setAuthorityIx, setImmutableIx, setAnotherAuthorityIx], tx), ); const promise = signAndSendTransaction(client, transactionMessage); - + // Then we expect the transaction to fail. const error = await t.throwsAsync(promise); t.true(isProgramMetadataError(error.cause, transactionMessage, PROGRAM_METADATA_ERROR__IMMUTABLE_METADATA_ACCOUNT));