diff --git a/programs/token-2022/src/instructions/extensions/token_group/instructions/initialize_group.rs b/programs/token-2022/src/instructions/extensions/token_group/instructions/initialize_group.rs new file mode 100644 index 00000000..db1d7eeb --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/instructions/initialize_group.rs @@ -0,0 +1,93 @@ +use { + crate::instructions::extensions::token_group::state::{ + offset_token_group_initialize_group as OFFSET, InstructionDiscriminatorTokenGroup, + }, + pinocchio::{ + account_info::AccountInfo, + cpi::invoke_signed, + instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, + }, +}; + +/// Initialize a new `Group` +/// +/// Assumes one has already initialized a mint for the group. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Group +/// 1. `[]` Mint +/// 2. `[signer]` Mint authority +pub struct InitializeGroup<'a, 'b> { + /// Group Account + pub group: &'a AccountInfo, + /// Mint Account + pub mint: &'a AccountInfo, + /// Mint authority + pub mint_authority: &'a AccountInfo, + /// Update authority for the group + pub update_authority: Option<&'b Pubkey>, + /// The maximum number of group members + pub max_size: u64, + /// Token Group Program + pub program_id: &'b Pubkey, +} + +impl InitializeGroup<'_, '_> { + #[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.group.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::readonly_signer(self.mint_authority.key()), + ]; + + let mut buffer = [0u8; OFFSET::END as usize]; + let data = + initialize_group_instruction_data(&mut buffer, self.update_authority, self.max_size); + + let instruction = Instruction { + program_id: self.program_id, + accounts: &account_metas, + data, + }; + + invoke_signed( + &instruction, + &[self.group, self.mint, self.mint_authority], + signers, + ) + } +} + +#[inline(always)] +fn initialize_group_instruction_data<'a>( + buffer: &'a mut [u8], + update_authority: Option<&Pubkey>, + max_size: u64, +) -> &'a [u8] { + let mut offset = OFFSET::START as usize; + + // Set discriminators + buffer[..offset].copy_from_slice( + &(InstructionDiscriminatorTokenGroup::InitializeGroup as u64).to_le_bytes(), + ); + + // Set update_authority + if let Some(x) = update_authority { + buffer[offset..offset + OFFSET::UPDATE_AUTHORITY as usize].copy_from_slice(x); + } + offset += OFFSET::UPDATE_AUTHORITY as usize; + + // Set max_size + buffer[offset..offset + OFFSET::MAX_SIZE as usize].copy_from_slice(&max_size.to_le_bytes()); + + buffer +} diff --git a/programs/token-2022/src/instructions/extensions/token_group/instructions/initialize_member.rs b/programs/token-2022/src/instructions/extensions/token_group/instructions/initialize_member.rs new file mode 100644 index 00000000..f1ada523 --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/instructions/initialize_member.rs @@ -0,0 +1,90 @@ +use { + crate::instructions::extensions::token_group::state::{ + offset_token_group_initialize_member as OFFSET, InstructionDiscriminatorTokenGroup, + }, + pinocchio::{ + account_info::AccountInfo, + cpi::invoke_signed, + instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, + }, +}; + +/// Initialize a new `Member` of a `Group` +/// +/// Assumes the `Group` has already been initialized, +/// as well as the mint for the member. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Member +/// 1. `[]` Member mint +/// 2. `[signer]` Member mint authority +/// 3. `[writable]` Group +/// 4. `[signer]` Group update authority +pub struct InitializeMember<'a, 'b> { + /// Member Account + pub member: &'a AccountInfo, + /// Member mint + pub member_mint: &'a AccountInfo, + /// Member mint authority + pub member_mint_authority: &'a AccountInfo, + /// Group Account + pub group: &'a AccountInfo, + /// Group update authority + pub group_update_authority: &'a AccountInfo, + /// Token Group Program + pub program_id: &'b Pubkey, +} + +impl InitializeMember<'_, '_> { + #[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.member.key()), + AccountMeta::readonly(self.member_mint.key()), + AccountMeta::readonly_signer(self.member_mint_authority.key()), + AccountMeta::writable(self.group.key()), + AccountMeta::readonly_signer(self.group_update_authority.key()), + ]; + + let mut buffer = [0u8; OFFSET::END as usize]; + let data = initialize_member_instruction_data(&mut buffer); + + let instruction = Instruction { + program_id: self.program_id, + accounts: &account_metas, + data, + }; + + invoke_signed( + &instruction, + &[ + self.member, + self.member_mint, + self.member_mint_authority, + self.group, + self.group_update_authority, + ], + signers, + ) + } +} + +#[inline(always)] +fn initialize_member_instruction_data<'a>(buffer: &'a mut [u8]) -> &'a [u8] { + let offset = OFFSET::START as usize; + + // Set discriminators + buffer[..offset].copy_from_slice( + &(InstructionDiscriminatorTokenGroup::InitializeMember as u64).to_le_bytes(), + ); + + buffer +} diff --git a/programs/token-2022/src/instructions/extensions/token_group/instructions/mod.rs b/programs/token-2022/src/instructions/extensions/token_group/instructions/mod.rs new file mode 100644 index 00000000..40fbe3e0 --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/instructions/mod.rs @@ -0,0 +1,9 @@ +mod initialize_group; +mod initialize_member; +mod update_group_authority; +mod update_max_size; + +pub use initialize_group::*; +pub use initialize_member::*; +pub use update_group_authority::*; +pub use update_max_size::*; diff --git a/programs/token-2022/src/instructions/extensions/token_group/instructions/update_group_authority.rs b/programs/token-2022/src/instructions/extensions/token_group/instructions/update_group_authority.rs new file mode 100644 index 00000000..8a9f0f53 --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/instructions/update_group_authority.rs @@ -0,0 +1,75 @@ +use { + crate::instructions::extensions::token_group::state::{ + offset_token_group_update_authority as OFFSET, InstructionDiscriminatorTokenGroup, + }, + pinocchio::{ + account_info::AccountInfo, + cpi::invoke_signed, + instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, + }, +}; + +/// Update the authority of a `Group` +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Group +/// 1. `[signer]` Current update authority +pub struct UpdateGroupAuthority<'a, 'b> { + /// Group Account + pub group: &'a AccountInfo, + /// Current update authority + pub current_authority: &'a AccountInfo, + /// New authority for the group, or None to unset + pub new_authority: Option<&'b Pubkey>, + /// Token Group Program + pub program_id: &'b Pubkey, +} + +impl UpdateGroupAuthority<'_, '_> { + #[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.group.key()), + AccountMeta::readonly_signer(self.current_authority.key()), + ]; + + let mut buffer = [0u8; OFFSET::END as usize]; + let data = update_group_authority_instruction_data(&mut buffer, self.new_authority); + + let instruction = Instruction { + program_id: self.program_id, + accounts: &account_metas, + data, + }; + + invoke_signed(&instruction, &[self.group, self.current_authority], signers) + } +} + +#[inline(always)] +fn update_group_authority_instruction_data<'a>( + buffer: &'a mut [u8], + new_authority: Option<&Pubkey>, +) -> &'a [u8] { + let offset = OFFSET::START as usize; + + // Set discriminators + buffer[..offset].copy_from_slice( + &(InstructionDiscriminatorTokenGroup::UpdateGroupAuthority as u64).to_le_bytes(), + ); + + // Set new_authority (optional) + if let Some(authority) = new_authority { + buffer[offset..offset + OFFSET::NEW_AUTHORITY as usize].copy_from_slice(authority.as_ref()); + } + + buffer +} diff --git a/programs/token-2022/src/instructions/extensions/token_group/instructions/update_max_size.rs b/programs/token-2022/src/instructions/extensions/token_group/instructions/update_max_size.rs new file mode 100644 index 00000000..d09848d2 --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/instructions/update_max_size.rs @@ -0,0 +1,70 @@ +use { + crate::instructions::extensions::token_group::state::{ + offset_token_group_update_max_size as OFFSET, InstructionDiscriminatorTokenGroup, + }, + pinocchio::{ + account_info::AccountInfo, + cpi::invoke_signed, + instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, + }, +}; + +/// Update the max size of a `Group` +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Group +/// 1. `[signer]` Update authority +pub struct UpdateGroupMaxSize<'a, 'b> { + /// Group Account + pub group: &'a AccountInfo, + // /// Update authority + pub update_authority: &'a AccountInfo, + /// New max size for the group + pub max_size: u64, + /// Token Group Program + pub program_id: &'b Pubkey, +} + +impl UpdateGroupMaxSize<'_, '_> { + #[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.group.key()), + AccountMeta::readonly_signer(self.update_authority.key()), + ]; + + let mut buffer = [0u8; OFFSET::END as usize]; + let data = update_group_max_size_instruction_data(&mut buffer, self.max_size); + + let instruction = Instruction { + program_id: self.program_id, + accounts: &account_metas, + data, + }; + + invoke_signed(&instruction, &[self.group, self.update_authority], signers) + } +} + +#[inline(always)] +fn update_group_max_size_instruction_data<'a>(buffer: &'a mut [u8], max_size: u64) -> &'a [u8] { + let offset = OFFSET::START as usize; + + // Set discriminators + buffer[..offset].copy_from_slice( + &(InstructionDiscriminatorTokenGroup::UpdateGroupMaxSize as u64).to_le_bytes(), + ); + + // Set max_size + buffer[offset..offset + OFFSET::MAX_SIZE as usize].copy_from_slice(&max_size.to_le_bytes()); + + buffer +} diff --git a/programs/token-2022/src/instructions/extensions/token_group/mod.rs b/programs/token-2022/src/instructions/extensions/token_group/mod.rs new file mode 100644 index 00000000..ec1f45c6 --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/mod.rs @@ -0,0 +1,5 @@ +pub mod instructions; +pub mod state; + +pub use instructions::*; +pub use state::*; diff --git a/programs/token-2022/src/instructions/extensions/token_group/state.rs b/programs/token-2022/src/instructions/extensions/token_group/state.rs new file mode 100644 index 00000000..0323594e --- /dev/null +++ b/programs/token-2022/src/instructions/extensions/token_group/state.rs @@ -0,0 +1,59 @@ +use pinocchio::pubkey::Pubkey; + +#[repr(u64)] +pub enum InstructionDiscriminatorTokenGroup { + InitializeGroup = 288286683834380665, // [121, 113, 108, 39, 54, 51, 0, 4] + UpdateGroupMaxSize = 7931435946663945580, // [108, 37, 171, 143, 248, 30, 18, 110] + UpdateGroupAuthority = 14688734194668431777, // [161, 105, 88, 1, 237, 221, 216, 203] + InitializeMember = 9688630243381616792, // [152, 32, 222, 176, 223, 237, 116, 134] +} + +/// Instruction data layout: +/// - [0..8] : Instruction discriminator (8 bytes) +/// - [8..40] : update_authority pubkey (32 bytes) +/// - [40..48] : max_size (8 bytes) +pub mod offset_token_group_initialize_group { + pub const START: u8 = 8; + pub const UPDATE_AUTHORITY: u8 = 32; + pub const MAX_SIZE: u8 = 8; + pub const END: u8 = START + UPDATE_AUTHORITY + MAX_SIZE; +} + +/// Instruction data layout: +/// - [0..8] : Instruction discriminator (8 bytes) +/// - [8..16] : max_size (8 bytes) +pub mod offset_token_group_update_max_size { + pub const START: u8 = 8; + pub const MAX_SIZE: u8 = 8; + pub const END: u8 = START + MAX_SIZE; +} + +/// Instruction data layout: +/// - [0..8] : Instruction discriminator (8 bytes) +/// - [8..40] : new_authority pubkey (32 bytes) +pub mod offset_token_group_update_authority { + pub const START: u8 = 8; + pub const NEW_AUTHORITY: u8 = 32; + pub const END: u8 = START + NEW_AUTHORITY; +} + +/// Instruction data layout: +/// - [0..8] : Instruction discriminator (8 bytes) +pub mod offset_token_group_initialize_member { + pub const START: u8 = 8; + pub const END: u8 = START; +} + +/// Data struct for a `TokenGroup` +#[repr(C)] +pub struct TokenGroup { + /// The authority that can sign to update the group + update_authority: Pubkey, + /// The associated mint, used to counter spoofing to be sure that group + /// belongs to a particular mint + mint: Pubkey, + /// The current number of group members + size: u64, + /// The maximum number of group members + max_size: u64, +}