use airdrop_api::{
consts::{CAMPAIGN_STATUS_ACTIVE, TREASURY},
instruction::DirectTransfer,
loaders::AirdropAccountInfoValidation,
pda::campaign_treasury_pda,
};
use airdrop_api::{AirdropError, Campaign, Config};
use solana_program::{clock::Clock, program::invoke, program_error::ProgramError, pubkey::Pubkey};
use steel::*;
pub fn process_direct_transfer(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = DirectTransfer::try_from_bytes(data)?;
let campaign_id = args.campaign_id;
let count = args.count as usize;
if count == 0 || count > 10 {
return Err(AirdropError::InvalidAmount.into());
}
let [owner_info, campaign_info, config_info, fee_account_info, treasury_info, treasury_tokens_info, mint_info, token_program, associated_token_program, system_program, recipient_accounts @ ..] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
owner_info.is_signer()?;
let config = config_info
.is_config()?
.is_writable()?
.as_account_mut::<Config>(&airdrop_api::ID)?;
fee_account_info
.is_writable()?
.has_address(&config.fee_account)?;
let campaign = campaign_info
.is_campaign()?
.as_account_mut::<Campaign>(&airdrop_api::ID)?
.assert_mut(|c| c.owner == *owner_info.key)?
.assert_mut(|c| c.status == CAMPAIGN_STATUS_ACTIVE)?;
let (expected_treasury, _treasury_bump) = campaign_treasury_pda(&campaign_id);
treasury_info.has_address(&expected_treasury)?;
treasury_tokens_info
.is_writable()?
.has_address(&campaign.treasury)?;
mint_info.has_address(&campaign.mint)?;
token_program.is_program(&spl_token::ID)?;
associated_token_program.is_program(&spl_associated_token_account::ID)?;
system_program.is_program(&system_program::ID)?;
let total_fee = config
.direct_transfer_fee_per_recipient_lamports
.checked_mul(count as u64)
.ok_or(ProgramError::ArithmeticOverflow)?;
if total_fee > 0 {
invoke(
&solana_program::system_instruction::transfer(
owner_info.key,
fee_account_info.key,
total_fee,
),
&[
owner_info.clone(),
fee_account_info.clone(),
system_program.clone(),
],
)?;
config.total_fees_collected = config
.total_fees_collected
.checked_add(total_fee)
.ok_or(ProgramError::ArithmeticOverflow)?;
}
let mut offset = std::mem::size_of::<DirectTransfer>();
let mut transfers = Vec::new();
for _ in 0..count {
if offset + 32 + 8 > data.len() {
return Err(ProgramError::InvalidInstructionData);
}
let recipient_bytes: [u8; 32] = data[offset..offset + 32]
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?;
let recipient =
Pubkey::try_from(recipient_bytes).map_err(|_| ProgramError::InvalidInstructionData)?;
let amount_bytes: [u8; 8] = data[offset + 32..offset + 32 + 8]
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?;
let amount = u64::from_le_bytes(amount_bytes);
if amount == 0 {
return Err(AirdropError::InvalidAmount.into());
}
transfers.push((recipient, amount));
offset += 32 + 8;
}
if recipient_accounts.len() < count {
return Err(ProgramError::NotEnoughAccountKeys);
}
let clock = Clock::get()?;
let mut total_transferred = 0u64;
for (i, (recipient, amount)) in transfers.iter().enumerate() {
let recipient_tokens_info = &recipient_accounts[i];
recipient_tokens_info.is_writable()?;
if recipient_tokens_info.data_is_empty() {
create_associated_token_account(
owner_info,
owner_info,
recipient_tokens_info,
mint_info,
system_program,
token_program,
associated_token_program,
)?;
}
let expected_recipient_tokens =
spl_associated_token_account::get_associated_token_address_with_program_id(
recipient,
&campaign.mint,
&spl_token::ID,
);
recipient_tokens_info.has_address(&expected_recipient_tokens)?;
transfer_signed(
treasury_info,
treasury_tokens_info,
recipient_tokens_info,
token_program,
*amount,
&[TREASURY, &campaign_id],
)?;
total_transferred = total_transferred
.checked_add(*amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
campaign.total_allocated = campaign.total_allocated
.checked_add(*amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
campaign.total_claimed = campaign.total_claimed
.checked_add(*amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
}
airdrop_api::event::TokensDirectTransferredEvent {
campaign: *campaign_info.key,
recipient_count: count as u8,
_padding1: [0u8; 7],
total_amount: total_transferred,
transferred_at: clock.unix_timestamp,
_padding2: [0u8; 8],
}
.log();
Ok(())
}