use {
crate::{
instructions::{
account_borrow_failed_error, initialize_multisig::MAX_MULTISIG_SIGNERS,
invalid_argument_error, write_bytes, CpiWriter, UNINIT_BYTE, UNINIT_CPI_ACCOUNT,
UNINIT_INSTRUCTION_ACCOUNT,
},
TokenInterface,
},
core::{marker::PhantomData, mem::MaybeUninit, slice::from_raw_parts},
solana_account_view::AccountView,
solana_address::Address,
solana_instruction_view::{
cpi::{invoke_signed_unchecked, CpiAccount, Signer},
InstructionAccount, InstructionView,
},
solana_program_error::{ProgramError, ProgramResult},
};
const DISCRIMINATOR: u8 = 4;
const MAX_ACCOUNTS_LEN: usize = 3 + MAX_MULTISIG_SIGNERS;
const DATA_LEN: usize = 9;
pub struct Approve<'account, 'multisig, MultisigSigner: AsRef<AccountView>, Program: TokenInterface>
{
pub source: &'account AccountView,
pub delegate: &'account AccountView,
pub authority: &'account AccountView,
pub multisig_signers: &'multisig [MultisigSigner],
pub amount: u64,
_program: PhantomData<Program>,
}
impl<'account, Program: TokenInterface> Approve<'account, '_, &'account AccountView, Program> {
pub const DISCRIMINATOR: u8 = DISCRIMINATOR;
pub const MAX_ACCOUNTS_LEN: usize = MAX_ACCOUNTS_LEN;
pub const DATA_LEN: usize = DATA_LEN;
#[inline(always)]
pub fn new(
source: &'account AccountView,
delegate: &'account AccountView,
authority: &'account AccountView,
amount: u64,
) -> Self {
Self::with_multisig_signers(source, delegate, authority, amount, &[])
}
}
impl<'account, 'multisig, MultisigSigner: AsRef<AccountView>, Program: TokenInterface>
Approve<'account, 'multisig, MultisigSigner, Program>
{
#[inline(always)]
pub fn with_multisig_signers(
source: &'account AccountView,
delegate: &'account AccountView,
authority: &'account AccountView,
amount: u64,
multisig_signers: &'multisig [MultisigSigner],
) -> Self {
Self {
source,
delegate,
authority,
multisig_signers,
amount,
_program: PhantomData,
}
}
#[inline(always)]
pub fn invoke(&self) -> ProgramResult {
self.invoke_with_unverified_program(&Program::ID)
}
#[inline(always)]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_with_unverified_program(signers, &Program::ID)
}
#[inline(always)]
pub fn invoke_with_program(&self, program: &Address) -> ProgramResult {
self.invoke_signed_with_program(&[], program)
}
#[inline(always)]
pub fn invoke_signed_with_program(
&self,
signers: &[Signer],
program: &Address,
) -> ProgramResult {
Program::verify(program)?;
self.invoke_signed_with_unverified_program(signers, program)
}
#[inline(always)]
pub fn invoke_with_unverified_program(&self, program: &Address) -> ProgramResult {
self.invoke_signed_with_unverified_program(&[], program)
}
#[inline(always)]
pub fn invoke_signed_with_unverified_program(
&self,
signers: &[Signer],
program: &Address,
) -> ProgramResult {
if self.multisig_signers.len() > MAX_MULTISIG_SIGNERS {
Err(ProgramError::InvalidArgument)?;
}
let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; MAX_ACCOUNTS_LEN];
let written_instruction_accounts =
self.write_instruction_accounts(&mut instruction_accounts)?;
let mut accounts = [UNINIT_CPI_ACCOUNT; MAX_ACCOUNTS_LEN];
let written_accounts = self.write_accounts(&mut accounts)?;
let mut instruction_data = [UNINIT_BYTE; DATA_LEN];
let written_instruction_data = self.write_instruction_data(&mut instruction_data)?;
unsafe {
invoke_signed_unchecked(
&InstructionView {
program_id: program,
accounts: from_raw_parts(
instruction_accounts.as_ptr() as _,
written_instruction_accounts,
),
data: from_raw_parts(instruction_data.as_ptr() as _, written_instruction_data),
},
from_raw_parts(accounts.as_ptr() as _, written_accounts),
signers,
);
}
Ok(())
}
}
impl<MultisigSigner: AsRef<AccountView>, Program: TokenInterface> CpiWriter
for Approve<'_, '_, MultisigSigner, Program>
{
#[inline(always)]
fn write_accounts<'cpi>(
&self,
accounts: &mut [MaybeUninit<CpiAccount<'cpi>>],
) -> Result<usize, ProgramError>
where
Self: 'cpi,
{
write_accounts(
self.source,
self.delegate,
self.authority,
self.multisig_signers,
accounts,
)
}
#[inline(always)]
fn write_instruction_accounts<'cpi>(
&self,
accounts: &mut [MaybeUninit<InstructionAccount<'cpi>>],
) -> Result<usize, ProgramError>
where
Self: 'cpi,
{
write_instruction_accounts(
self.source,
self.delegate,
self.authority,
self.multisig_signers,
accounts,
)
}
#[inline(always)]
fn write_instruction_data(&self, data: &mut [MaybeUninit<u8>]) -> Result<usize, ProgramError> {
write_instruction_data(self.amount, data)
}
}
impl<MultisigSigner: AsRef<AccountView>, Program: TokenInterface> super::batch::IntoBatch<Program>
for Approve<'_, '_, MultisigSigner, Program>
{
#[inline(always)]
fn into_batch<'account, 'state>(
self,
batch: &mut super::batch::Batch<'account, 'state, Program>,
) -> ProgramResult
where
Self: 'account + 'state,
{
let Self {
source,
delegate,
authority,
multisig_signers,
amount,
..
} = self;
batch.push(
|accounts| write_accounts(source, delegate, authority, multisig_signers, accounts),
|accounts| {
write_instruction_accounts(source, delegate, authority, multisig_signers, accounts)
},
|data| write_instruction_data(amount, data),
)
}
}
#[inline(always)]
fn write_accounts<'account, 'multisig, 'out, MultisigSigner: AsRef<AccountView>>(
source: &'account AccountView,
delegate: &'account AccountView,
authority: &'account AccountView,
multisig_signers: &'multisig [MultisigSigner],
accounts: &mut [MaybeUninit<CpiAccount<'out>>],
) -> Result<usize, ProgramError>
where
'account: 'out,
'multisig: 'out,
{
let expected_accounts = 3 + multisig_signers.len();
if expected_accounts > accounts.len() {
return Err(invalid_argument_error());
}
if source.is_borrowed() {
return Err(account_borrow_failed_error());
}
CpiAccount::init_from_account_view(source, &mut accounts[0]);
CpiAccount::init_from_account_view(delegate, &mut accounts[1]);
CpiAccount::init_from_account_view(authority, &mut accounts[2]);
for (account, signer) in accounts[3..expected_accounts]
.iter_mut()
.zip(multisig_signers.iter())
{
CpiAccount::init_from_account_view(signer.as_ref(), account);
}
Ok(expected_accounts)
}
#[inline(always)]
fn write_instruction_accounts<'account, 'multisig, 'out, MultisigSigner: AsRef<AccountView>>(
source: &'account AccountView,
delegate: &'account AccountView,
authority: &'account AccountView,
multisig_signers: &'multisig [MultisigSigner],
accounts: &mut [MaybeUninit<InstructionAccount<'out>>],
) -> Result<usize, ProgramError>
where
'account: 'out,
'multisig: 'out,
{
let expected_accounts = 3 + multisig_signers.len();
if expected_accounts > accounts.len() {
return Err(invalid_argument_error());
}
accounts[0].write(InstructionAccount::writable(source.address()));
accounts[1].write(InstructionAccount::readonly(delegate.address()));
accounts[2].write(InstructionAccount::new(
authority.address(),
false,
multisig_signers.is_empty(),
));
for (account, signer) in accounts[3..expected_accounts]
.iter_mut()
.zip(multisig_signers.iter())
{
account.write(InstructionAccount::readonly_signer(
signer.as_ref().address(),
));
}
Ok(expected_accounts)
}
#[inline(always)]
fn write_instruction_data(
amount: u64,
data: &mut [MaybeUninit<u8>],
) -> Result<usize, ProgramError> {
if data.len() < DATA_LEN {
return Err(invalid_argument_error());
}
data[0].write(DISCRIMINATOR);
write_bytes(&mut data[1..DATA_LEN], &amount.to_le_bytes());
Ok(DATA_LEN)
}