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,82 @@
use {
crate::instructions::extensions::{
group_pointer::state::{
offset_group_pointer_initialize as OFFSET, InstructionDiscriminatorGroupPointer,
},
ExtensionDiscriminator,
},
pinocchio::{
account_info::AccountInfo,
cpi::invoke_signed,
instruction::{AccountMeta, Instruction, Signer},
pubkey::Pubkey,
ProgramResult,
},
};

/// Initialize a new mint with a group pointer
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The mint to initialize.
pub struct Initialize<'a, 'b> {
/// Mint Account
pub mint: &'a AccountInfo,
/// Optional authority that can set the group address
pub authority: Option<&'b Pubkey>,
/// Optional account address that holds the group
pub group_address: Option<&'b Pubkey>,
/// Token Program
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.group_address);

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

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

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

// Set discriminators (GroupPointer + Initialize)
buffer[..offset].copy_from_slice(&[
ExtensionDiscriminator::GroupPointer as u8,
InstructionDiscriminatorGroupPointer::Initialize as u8,
]);

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

// Set group_address
if let Some(x) = group_address {
buffer[offset..offset + OFFSET::GROUP_ADDRESS_PUBKEY as usize].copy_from_slice(x);
}

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

pub use initialize::*;
pub use update::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use {
crate::{
instructions::extensions::{
group_pointer::state::{
offset_group_pointer_update as OFFSET, InstructionDiscriminatorGroupPointer,
},
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 group pointer address. Only supported for mints that
/// include the `GroupPointer` extension.
///
/// Accounts expected by this instruction:
///
/// * Single authority
/// 0. `[writable]` The mint.
/// 1. `[signer]` The group pointer authority.
///
/// * Multisignature authority
/// 0. `[writable]` The mint.
/// 1. `[]` The mint's group pointer authority.
/// 2. `..2+M` `[signer]` M signer accounts.
pub struct Update<'a, 'b> {
/// Mint Account
pub mint: &'a AccountInfo,
/// The group pointer authority.
pub authority: &'a AccountInfo,
/// The new account address that holds the group
pub group_address: Option<&'b Pubkey>,
/// The Signer accounts if `authority` is a multisig
pub signers: &'a [AccountInfo],
/// Token Program
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: account_signers,
token_program,
..
} = self;

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

let num_accounts = 2 + account_signers.len();

// Account metadata
const UNINIT_META: MaybeUninit<AccountMeta> = MaybeUninit::<AccountMeta>::uninit();
let mut acc_metas = [UNINIT_META; 2 + MAX_MULTISIG_SIGNERS];

unsafe {
// SAFETY:
// - `account_metas` is sized to 2 + MAX_MULTISIG_SIGNERS
// - Index 0 is always present
acc_metas
.get_unchecked_mut(0)
.write(AccountMeta::writable(mint.key()));
// - Index 1 is always present
if account_signers.is_empty() {
acc_metas
.get_unchecked_mut(1)
.write(AccountMeta::readonly_signer(authority.key()));
} else {
acc_metas
.get_unchecked_mut(1)
.write(AccountMeta::readonly(authority.key()));
}
}

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

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

let instruction = Instruction {
program_id: token_program,
accounts: unsafe { slice::from_raw_parts(acc_metas.as_ptr() as _, num_accounts) },
data,
};

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

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

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

invoke_signed_with_bounds::<{ 2 + MAX_MULTISIG_SIGNERS }>(
&instruction,
unsafe { slice::from_raw_parts(acc_infos.as_ptr() as _, num_accounts) },
signers,
)
}
}

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

// Set discriminators (GroupPointer + Update)
buffer[..offset].copy_from_slice(&[
ExtensionDiscriminator::GroupPointer as u8,
InstructionDiscriminatorGroupPointer::Update as u8,
]);

// Set group_address
if let Some(x) = group_address {
buffer[offset..offset + OFFSET::GROUP_ADDRESS_PUBKEY as usize].copy_from_slice(x);
}

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

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

#[repr(u8)]
pub enum InstructionDiscriminatorGroupPointer {
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] : group_address pubkey (32 bytes)
pub mod offset_group_pointer_initialize {
pub const START: u8 = 2;
pub const AUTHORITY_PUBKEY: u8 = 32;
pub const GROUP_ADDRESS_PUBKEY: u8 = 32;
pub const END: u8 = START + AUTHORITY_PUBKEY + GROUP_ADDRESS_PUBKEY;
}

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

#[repr(C)]
pub struct GroupPointer {
/// Authority that can set the group address
authority: Pubkey,
/// Account address that holds the group
group_address: Pubkey,
}