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

pub struct InitializeTransferHook<'a, 'b> {
/// Mint Account to initialize.
pub mint_account: &'a AccountInfo,
/// Optional authority that can set the transfer hook program id
pub authority: Option<&'b Pubkey>,
/// Program that authorizes the transfer
pub program_id: Option<&'b Pubkey>,
/// Token Program
pub token_program: &'b Pubkey,
}

impl InitializeTransferHook<'_, '_> {
#[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_account.key())];

let mut buffer = [0u8; 66];
let data = initialize_instruction_data(&mut buffer, self.authority, self.program_id);

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

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

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

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

// Set authority at offset [2..34]
if let Some(x) = authority {
buffer[offset..offset + OFFSET::AUTHORITY_PUBKEY as usize].copy_from_slice(x);
} else {
buffer[offset..offset + OFFSET::AUTHORITY_PUBKEY as usize].copy_from_slice(&[0; 32]);
}

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

// Set program_id at offset [34..66]
if let Some(x) = program_id {
buffer[offset..OFFSET::END as usize].copy_from_slice(x);
} else {
buffer[offset..OFFSET::END as usize].copy_from_slice(&[0; 32]);
}

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

pub use initialize::*;
pub use update::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use {
crate::{
instructions::extensions::{
transfer_hook::state::{
offset_transfer_hook_update as OFFSET, TransferHookInstruction,
},
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,
},
};

pub struct UpdateTransferHook<'a, 'b> {
/// Mint Account to update.
pub mint_account: &'a AccountInfo,
/// Authority Account.
pub authority: &'a AccountInfo,
/// Signer Accounts (for multisig support)
pub signers: &'a [AccountInfo],
/// Program that authorizes the transfer
pub program_id: Option<&'b Pubkey>,
/// Token Program
pub token_program: &'b Pubkey,
}

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

#[inline(always)]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let &Self {
mint_account,
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_account.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; 34];
let data = update_instruction_data(&mut buffer, self.program_id);

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_account);
// - 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], program_id: Option<&Pubkey>) -> &'a [u8] {
let offset = OFFSET::START as usize;

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

// Set program_id at offset [2..34]
if let Some(x) = program_id {
buffer[offset..OFFSET::END as usize].copy_from_slice(x);
} else {
buffer[offset..OFFSET::END as usize].copy_from_slice(&[0; 32]);
}

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,39 @@
use pinocchio::pubkey::Pubkey;

#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransferHookInstruction {
Initialize,
Update,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TransferHook {
/// Authority that can set the transfer hook program id
authority: Pubkey,
/// Program that authorizes the transfer
program_id: Pubkey,
}

/// Instruction data layout:
/// - [0]: instruction discriminator (1 byte, u8)
/// - [1]: instruction_type (1 byte, u8)
/// - [2..34]: authority (32 bytes, Pubkey)
/// - [34..66]: program_id (32 bytes, Pubkey)
pub mod offset_transfer_hook_initialize {
pub const START: u8 = 2;
pub const AUTHORITY_PUBKEY: u8 = 32;
pub const PROGRAM_ID_PUBKEY: u8 = 32;
pub const END: u8 = START + AUTHORITY_PUBKEY + PROGRAM_ID_PUBKEY;
}

/// Instruction data layout:
/// - [0]: instruction discriminator (1 byte, u8)
/// - [1]: instruction_type (1 byte, u8)
/// - [2..34]: program_id (32 bytes, Pubkey)
pub mod offset_transfer_hook_update {
pub const START: u8 = 2;
pub const PROGRAM_ID_PUBKEY: u8 = 32;
pub const END: u8 = START + PROGRAM_ID_PUBKEY;
}