Skip to content

Commit 2254041

Browse files
authored
Merge pull request #28 from burhankhaja/group-pointer
Add group pointer extension
2 parents bde8488 + cf164d0 commit 2254041

File tree

5 files changed

+277
-0
lines changed

5 files changed

+277
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use {
2+
crate::instructions::extensions::{
3+
group_pointer::state::{
4+
offset_group_pointer_initialize as OFFSET, InstructionDiscriminatorGroupPointer,
5+
},
6+
ExtensionDiscriminator,
7+
},
8+
pinocchio::{
9+
account_info::AccountInfo,
10+
cpi::invoke_signed,
11+
instruction::{AccountMeta, Instruction, Signer},
12+
pubkey::Pubkey,
13+
ProgramResult,
14+
},
15+
};
16+
17+
/// Initialize a new mint with a group pointer
18+
///
19+
/// Accounts expected by this instruction:
20+
///
21+
/// 0. `[writable]` The mint to initialize.
22+
pub struct Initialize<'a, 'b> {
23+
/// Mint Account
24+
pub mint: &'a AccountInfo,
25+
/// Optional authority that can set the group address
26+
pub authority: Option<&'b Pubkey>,
27+
/// Optional account address that holds the group
28+
pub group_address: Option<&'b Pubkey>,
29+
/// Token Program
30+
pub token_program: &'b Pubkey,
31+
}
32+
33+
impl Initialize<'_, '_> {
34+
#[inline(always)]
35+
pub fn invoke(&self) -> ProgramResult {
36+
self.invoke_signed(&[])
37+
}
38+
39+
#[inline(always)]
40+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
41+
let account_metas = [AccountMeta::writable(self.mint.key())];
42+
43+
let mut buffer = [0u8; OFFSET::END as usize];
44+
let data = initialize_instruction_data(&mut buffer, self.authority, self.group_address);
45+
46+
let instruction = Instruction {
47+
program_id: self.token_program,
48+
accounts: &account_metas,
49+
data,
50+
};
51+
52+
invoke_signed(&instruction, &[self.mint], signers)
53+
}
54+
}
55+
56+
#[inline(always)]
57+
fn initialize_instruction_data<'a>(
58+
buffer: &'a mut [u8],
59+
authority: Option<&Pubkey>,
60+
group_address: Option<&Pubkey>,
61+
) -> &'a [u8] {
62+
let mut offset = OFFSET::START as usize;
63+
64+
// Set discriminators (GroupPointer + Initialize)
65+
buffer[..offset].copy_from_slice(&[
66+
ExtensionDiscriminator::GroupPointer as u8,
67+
InstructionDiscriminatorGroupPointer::Initialize as u8,
68+
]);
69+
70+
// Set authority
71+
if let Some(x) = authority {
72+
buffer[offset..offset + OFFSET::AUTHORITY_PUBKEY as usize].copy_from_slice(x);
73+
}
74+
offset += OFFSET::AUTHORITY_PUBKEY as usize;
75+
76+
// Set group_address
77+
if let Some(x) = group_address {
78+
buffer[offset..offset + OFFSET::GROUP_ADDRESS_PUBKEY as usize].copy_from_slice(x);
79+
}
80+
81+
buffer
82+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod initialize;
2+
mod update;
3+
4+
pub use initialize::*;
5+
pub use update::*;
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use {
2+
crate::{
3+
instructions::extensions::{
4+
group_pointer::state::{
5+
offset_group_pointer_update as OFFSET, InstructionDiscriminatorGroupPointer,
6+
},
7+
ExtensionDiscriminator,
8+
},
9+
instructions::MAX_MULTISIG_SIGNERS,
10+
},
11+
core::{mem::MaybeUninit, slice},
12+
pinocchio::{
13+
account_info::AccountInfo,
14+
cpi::invoke_signed_with_bounds,
15+
instruction::{AccountMeta, Instruction, Signer},
16+
program_error::ProgramError,
17+
pubkey::Pubkey,
18+
ProgramResult,
19+
},
20+
};
21+
22+
/// Update the group pointer address. Only supported for mints that
23+
/// include the `GroupPointer` extension.
24+
///
25+
/// Accounts expected by this instruction:
26+
///
27+
/// * Single authority
28+
/// 0. `[writable]` The mint.
29+
/// 1. `[signer]` The group pointer authority.
30+
///
31+
/// * Multisignature authority
32+
/// 0. `[writable]` The mint.
33+
/// 1. `[]` The mint's group pointer authority.
34+
/// 2. `..2+M` `[signer]` M signer accounts.
35+
pub struct Update<'a, 'b> {
36+
/// Mint Account
37+
pub mint: &'a AccountInfo,
38+
/// The group pointer authority.
39+
pub authority: &'a AccountInfo,
40+
/// The new account address that holds the group
41+
pub group_address: Option<&'b Pubkey>,
42+
/// The Signer accounts if `authority` is a multisig
43+
pub signers: &'a [AccountInfo],
44+
/// Token Program
45+
pub token_program: &'b Pubkey,
46+
}
47+
48+
impl Update<'_, '_> {
49+
#[inline(always)]
50+
pub fn invoke(&self) -> ProgramResult {
51+
self.invoke_signed(&[])
52+
}
53+
54+
#[inline(always)]
55+
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
56+
let &Self {
57+
mint,
58+
authority,
59+
signers: account_signers,
60+
token_program,
61+
..
62+
} = self;
63+
64+
if account_signers.len() > MAX_MULTISIG_SIGNERS {
65+
Err(ProgramError::InvalidArgument)?;
66+
}
67+
68+
let num_accounts = 2 + account_signers.len();
69+
70+
// Account metadata
71+
const UNINIT_META: MaybeUninit<AccountMeta> = MaybeUninit::<AccountMeta>::uninit();
72+
let mut acc_metas = [UNINIT_META; 2 + MAX_MULTISIG_SIGNERS];
73+
74+
unsafe {
75+
// SAFETY:
76+
// - `account_metas` is sized to 2 + MAX_MULTISIG_SIGNERS
77+
// - Index 0 is always present
78+
acc_metas
79+
.get_unchecked_mut(0)
80+
.write(AccountMeta::writable(mint.key()));
81+
// - Index 1 is always present
82+
if account_signers.is_empty() {
83+
acc_metas
84+
.get_unchecked_mut(1)
85+
.write(AccountMeta::readonly_signer(authority.key()));
86+
} else {
87+
acc_metas
88+
.get_unchecked_mut(1)
89+
.write(AccountMeta::readonly(authority.key()));
90+
}
91+
}
92+
93+
for (account_meta, signer) in acc_metas[2..].iter_mut().zip(account_signers.iter()) {
94+
account_meta.write(AccountMeta::readonly_signer(signer.key()));
95+
}
96+
97+
let mut buffer = [0u8; OFFSET::END as usize];
98+
let data = update_instruction_data(&mut buffer, self.group_address);
99+
100+
let instruction = Instruction {
101+
program_id: token_program,
102+
accounts: unsafe { slice::from_raw_parts(acc_metas.as_ptr() as _, num_accounts) },
103+
data,
104+
};
105+
106+
// Account info array
107+
const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit();
108+
let mut acc_infos = [UNINIT_INFO; 2 + MAX_MULTISIG_SIGNERS];
109+
110+
unsafe {
111+
// SAFETY:
112+
// - `account_infos` is sized to 2 + MAX_MULTISIG_SIGNERS
113+
// - Index 0 is always present
114+
acc_infos.get_unchecked_mut(0).write(mint);
115+
// - Index 1 is always present
116+
acc_infos.get_unchecked_mut(1).write(authority);
117+
}
118+
119+
// Fill signer accounts
120+
for (account_info, signer) in acc_infos[2..].iter_mut().zip(account_signers.iter()) {
121+
account_info.write(signer);
122+
}
123+
124+
invoke_signed_with_bounds::<{ 2 + MAX_MULTISIG_SIGNERS }>(
125+
&instruction,
126+
unsafe { slice::from_raw_parts(acc_infos.as_ptr() as _, num_accounts) },
127+
signers,
128+
)
129+
}
130+
}
131+
132+
#[inline(always)]
133+
fn update_instruction_data<'a>(buffer: &'a mut [u8], group_address: Option<&Pubkey>) -> &'a [u8] {
134+
let offset = OFFSET::START as usize;
135+
136+
// Set discriminators (GroupPointer + Update)
137+
buffer[..offset].copy_from_slice(&[
138+
ExtensionDiscriminator::GroupPointer as u8,
139+
InstructionDiscriminatorGroupPointer::Update as u8,
140+
]);
141+
142+
// Set group_address
143+
if let Some(x) = group_address {
144+
buffer[offset..offset + OFFSET::GROUP_ADDRESS_PUBKEY as usize].copy_from_slice(x);
145+
}
146+
147+
buffer
148+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod instructions;
2+
pub mod state;
3+
4+
pub use instructions::*;
5+
pub use state::*;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use pinocchio::pubkey::Pubkey;
2+
3+
#[repr(u8)]
4+
pub enum InstructionDiscriminatorGroupPointer {
5+
Initialize = 0,
6+
Update = 1,
7+
}
8+
9+
/// Instruction data layout:
10+
/// - [0] : Extension discriminator (1 byte)
11+
/// - [1] : Instruction discriminator (1 byte)
12+
/// - [2..34] : authority pubkey (32 bytes)
13+
/// - [34..66] : group_address pubkey (32 bytes)
14+
pub mod offset_group_pointer_initialize {
15+
pub const START: u8 = 2;
16+
pub const AUTHORITY_PUBKEY: u8 = 32;
17+
pub const GROUP_ADDRESS_PUBKEY: u8 = 32;
18+
pub const END: u8 = START + AUTHORITY_PUBKEY + GROUP_ADDRESS_PUBKEY;
19+
}
20+
21+
/// Instruction data layout:
22+
/// - [0]: Extension discriminator (1 byte, u8)
23+
/// - [1]: Instruction discriminator (1 byte, u8)
24+
/// - [2..34]: group_address pubkey (optional, 32 bytes)
25+
pub mod offset_group_pointer_update {
26+
pub const START: u8 = 2;
27+
pub const GROUP_ADDRESS_PUBKEY: u8 = 32;
28+
pub const END: u8 = START + GROUP_ADDRESS_PUBKEY;
29+
}
30+
31+
#[repr(C)]
32+
pub struct GroupPointer {
33+
/// Authority that can set the group address
34+
authority: Pubkey,
35+
/// Account address that holds the group
36+
group_address: Pubkey,
37+
}

0 commit comments

Comments
 (0)