Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use {
crate::instructions::extensions::{
metadata_pointer::state::{
offset_metadata_pointer_initialize as OFFSET, InstructionDiscriminatorMetadataPointer,
},
ExtensionDiscriminator,
},
pinocchio::{
account_info::AccountInfo,
cpi::invoke_signed,
instruction::{AccountMeta, Instruction, Signer},
pubkey::Pubkey,
ProgramResult,
},
};

/// Initialize a new mint with a metadata pointer
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The mint to initialize.
pub struct Initialize<'a, 'b> {
/// The mint to initialize with the metadata pointer extension.
pub mint: &'a AccountInfo,
/// Optional authority that can later update the metadata address.
pub authority: Option<&'b Pubkey>,
/// Optional initial metadata address.
pub metadata_address: Option<&'b Pubkey>,
/// Token program (Token-2022).
pub token_program: &'b Pubkey,
}

impl Initialize<'_, '_> {
#[inline(always)]
pub fn invoke(&self) -> ProgramResult {
self.invoke_signed(&[])
}

#[inline(always)]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let account_metas = [AccountMeta::writable(self.mint.key())];

let mut buffer = [0u8; OFFSET::END as usize];
let data = initialize_instruction_data(&mut buffer, self.authority, self.metadata_address);

let instruction = Instruction {
program_id: self.token_program,
data,
accounts: &account_metas,
};

invoke_signed(&instruction, &[self.mint], signers)
}
}

#[inline(always)]
fn initialize_instruction_data<'a>(
buffer: &'a mut [u8],
authority: Option<&Pubkey>,
metadata_address: Option<&Pubkey>,
) -> &'a [u8] {
let mut offset = OFFSET::START as usize;

// Encode discriminators (Metadata + Initialize)
buffer[..offset].copy_from_slice(&[
ExtensionDiscriminator::MetadataPointer as u8,
InstructionDiscriminatorMetadataPointer::Initialize as u8,
]);

// write authority pubkey bytes
if let Some(authority) = authority {
buffer[offset..offset + OFFSET::AUTHORITY_PUBKEY as usize].copy_from_slice(authority);
}

// shift offset past authority pubkey
offset += OFFSET::AUTHORITY_PUBKEY as usize;

// write metadata_address pubkey bytes
if let Some(metadata_address) = metadata_address {
buffer[offset..offset + OFFSET::METADATA_ADDRESS_PUBKEY as usize]
.copy_from_slice(metadata_address);
}

buffer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod initialize;
mod update;

pub use {initialize::*, update::*};
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use {
crate::{
instructions::extensions::{
metadata_pointer::state::{
offset_metadata_pointer_update as OFFSET, InstructionDiscriminatorMetadataPointer,
},
ExtensionDiscriminator,
},
instructions::MAX_MULTISIG_SIGNERS,
},
core::{mem::MaybeUninit, slice},
pinocchio::{
account_info::AccountInfo,
cpi::invoke_signed_with_bounds,
instruction::{AccountMeta, Instruction, Signer},
program_error::ProgramError,
pubkey::Pubkey,
ProgramResult,
},
};

/// Update the metadata pointer address. Only supported for mints that
/// include the `MetadataPointer` extension.
///
/// Accounts expected by this instruction:
///
/// * Single authority
/// 0. `[writable]` The mint.
/// 1. `[signer]` The metadata pointer authority.
///
/// * Multisignature authority
/// 0. `[writable]` The mint.
/// 1. `[]` The mint's metadata pointer authority.
/// 2. `..2+M` `[signer]` M signer accounts.
pub struct Update<'a, 'b> {
/// The mint to update.
pub mint: &'a AccountInfo,
/// The metadata pointer authority.
pub authority: &'a AccountInfo,
/// New metadata address (use `None` to clear).
pub new_metadata_address: Option<&'b Pubkey>,
/// The Signer accounts if `authority` is a multisig.
pub signers: &'a [AccountInfo],
/// Token program (Token-2022).
pub token_program: &'b Pubkey,
}

impl Update<'_, '_> {
#[inline(always)]
pub fn invoke(&self) -> ProgramResult {
self.invoke_signed(&[])
}

#[inline(always)]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let &Self {
mint,
authority,
signers: multisig_accounts,
token_program,
..
} = self;

if multisig_accounts.len() > MAX_MULTISIG_SIGNERS {
Err(ProgramError::InvalidArgument)?;
}

const UNINIT_ACCOUNT_METAS: MaybeUninit<AccountMeta> = MaybeUninit::<AccountMeta>::uninit();
let mut account_metas = [UNINIT_ACCOUNT_METAS; 2 + MAX_MULTISIG_SIGNERS];

unsafe {
// SAFETY:
// - `account_metas` is sized to 2 + MAX_MULTISIG_SIGNERS

// - Index 0 is always present (Mint)
account_metas
.get_unchecked_mut(0)
.write(AccountMeta::writable(mint.key()));

// - Index 1 is always present (Authority)
if multisig_accounts.is_empty() {
account_metas
.get_unchecked_mut(1)
.write(AccountMeta::readonly_signer(authority.key()));
} else {
account_metas
.get_unchecked_mut(1)
.write(AccountMeta::readonly(authority.key()));
}
}

for (account_meta, signer) in account_metas[2..].iter_mut().zip(multisig_accounts.iter()) {
account_meta.write(AccountMeta::readonly_signer(signer.key()));
}

// build instruction
let mut buffer = [0u8; OFFSET::END as usize];
let data = update_instruction_data(&mut buffer, self.new_metadata_address);

let num_accounts = 2 + multisig_accounts.len();

let instruction = Instruction {
program_id: token_program,
data: data,
accounts: unsafe {
slice::from_raw_parts(account_metas.as_ptr() as *const AccountMeta, num_accounts)
},
};

// Account info array
const UNINIT_ACCOUNT_INFOS: MaybeUninit<&AccountInfo> =
MaybeUninit::<&AccountInfo>::uninit();
let mut account_infos = [UNINIT_ACCOUNT_INFOS; 2 + MAX_MULTISIG_SIGNERS];

unsafe {
// SAFETY:
// - `account_infos` is sized to 2 + MAX_MULTISIG_SIGNERS
// - Index 0 is always present
account_infos.get_unchecked_mut(0).write(mint);
// - Index 1 is always present
account_infos.get_unchecked_mut(1).write(authority);
}

// Fill signer accounts
for (account_info, signer) in account_infos[2..].iter_mut().zip(multisig_accounts.iter()) {
account_info.write(signer);
}

invoke_signed_with_bounds::<{ 2 + MAX_MULTISIG_SIGNERS }>(
&instruction,
unsafe {
slice::from_raw_parts(account_infos.as_ptr() as *const &AccountInfo, num_accounts)
},
signers,
)
}
}

#[inline(always)]
fn update_instruction_data<'a>(
buffer: &'a mut [u8],
new_metadata_address: Option<&Pubkey>,
) -> &'a [u8] {
let offset = OFFSET::START as usize;

// Encode discriminators (Metadata + Update)
buffer[..offset].copy_from_slice(&[
ExtensionDiscriminator::MetadataPointer as u8,
InstructionDiscriminatorMetadataPointer::Update as u8,
]);

// write new_metadata_address pubkey bytes
if let Some(new_metadata_address) = new_metadata_address {
buffer[offset..offset + OFFSET::METADATA_ADDRESS_PUBKEY as usize]
.copy_from_slice(new_metadata_address);
}

buffer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod instructions;
pub mod state;

pub use {instructions::*, state::*};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use pinocchio::pubkey::Pubkey;

#[repr(u8)]
pub enum InstructionDiscriminatorMetadataPointer {
Initialize = 0,
Update = 1,
}

/// Instruction data layout:
/// - [0] : Extension discriminator (1 byte)
/// - [1] : Instruction discriminator (1 byte)
/// - [2..34] : authority pubkey (32 bytes)
/// - [34..66] : metadata_address pubkey (32 bytes)
pub mod offset_metadata_pointer_initialize {
pub const START: u8 = 2;
pub const AUTHORITY_PUBKEY: u8 = 32;
pub const METADATA_ADDRESS_PUBKEY: u8 = 32;
pub const END: u8 = START + AUTHORITY_PUBKEY + METADATA_ADDRESS_PUBKEY;
}

/// Instruction data layout:
/// - [0] : Extension discriminator (1 byte)
/// - [1] : Instruction discriminator (1 byte)
/// - [2..34] : metadata_address pubkey (32 bytes)
pub mod offset_metadata_pointer_update {
pub const START: u8 = 2;
pub const METADATA_ADDRESS_PUBKEY: u8 = 32;
pub const END: u8 = START + METADATA_ADDRESS_PUBKEY;
}

/// Metadata pointer extension data for mints.
#[repr(C)]
pub struct MetadataPointer {
/// Authority that can set the metadata address
pub authority: Pubkey,
/// Account address that holds the metadata
pub metadata_address: Pubkey,
}