use airdrop_api::{
consts::{ALLOCATION, CAMPAIGN_STATUS_ACTIVE},
instruction::Allocate,
loaders::AirdropAccountInfoValidation,
pda::{allocation_pda, campaign_pda},
prelude::*,
};
use airdrop_api::{Allocation, Campaign, Config};
use solana_program::{clock::Clock, program::invoke, program_error::ProgramError};
use steel::*;
pub fn process_allocate(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = Allocate::try_from_bytes(data)?;
let campaign_id = args.campaign_id;
let count = args.count as usize;
let [owner_info, campaign_info, config_info, fee_account_info, treasury_tokens_info, token_program, system_program, allocation_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 (expected_campaign, _campaign_bump) = campaign_pda(&campaign_id);
campaign_info
.is_campaign()?
.has_address(&expected_campaign)?;
let campaign = campaign_info
.as_account_mut::<Campaign>(&airdrop_api::ID)?
.assert_mut(|c| c.owner == *owner_info.key)?
.assert_mut(|c| c.status == CAMPAIGN_STATUS_ACTIVE)?;
treasury_tokens_info
.is_writable()?
.has_address(&campaign.treasury)?;
token_program.is_program(&spl_token::ID)?;
system_program.is_program(&system_program::ID)?;
let total_fee = config
.allocation_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::<Allocate>();
let mut allocations = 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());
}
allocations.push((recipient, amount));
offset += 32 + 8;
}
if allocation_accounts.len() < count {
return Err(ProgramError::NotEnoughAccountKeys);
}
let clock = Clock::get()?;
for (i, (recipient, amount)) in allocations.iter().enumerate() {
let allocation_info = &allocation_accounts[i];
let (expected_allocation, _) = allocation_pda(*campaign_info.key, *recipient);
allocation_info
.is_empty()?
.is_writable()?
.has_address(&expected_allocation)?;
create_program_account::<Allocation>(
allocation_info,
system_program,
owner_info,
&airdrop_api::ID,
&[ALLOCATION, campaign_info.key.as_ref(), recipient.as_ref()],
)?;
let allocation = allocation_info.as_account_mut::<Allocation>(&airdrop_api::ID)?;
allocation.campaign = *campaign_info.key;
allocation.recipient = *recipient;
allocation.amount = *amount;
allocation.claimed = 0;
allocation.allocated_at = clock.unix_timestamp;
allocation.claimed_at = 0;
campaign.total_allocated = campaign
.total_allocated
.checked_add(*amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
TokensAllocatedEvent {
campaign: *campaign_info.key,
recipient: *recipient,
amount: *amount,
}
.log();
}
Ok(())
}