use airdrop_api::{
consts::{CAMPAIGN_STATUS_ACTIVE, MAX_MERKLE_PROOF_LENGTH, TREASURY},
instruction::Claim,
loaders::AirdropAccountInfoValidation,
merkle::{verify_merkle_proof, MerkleProof},
pda::{allocation_pda, campaign_pda, campaign_treasury_pda},
prelude::*,
};
use airdrop_api::{Allocation, Campaign};
use solana_program::clock::Clock;
use steel::*;
pub fn process_claim(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = Claim::try_from_bytes(data)?;
let campaign_id = args.campaign_id;
let method = args.method;
let amount = u64::from_le_bytes(args.amount);
let [recipient_info, campaign_info, allocation_or_empty_info, treasury_info, treasury_tokens_info, recipient_tokens_info, mint_info, token_program, associated_token_program, system_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
recipient_info.is_signer()?;
let (expected_campaign, _campaign_bump) = campaign_pda(&campaign_id);
let campaign = campaign_info
.is_campaign()?
.has_address(&expected_campaign)?
.as_account_mut::<Campaign>(&airdrop_api::ID)?
.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)?;
recipient_tokens_info.is_writable()?;
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 claim_amount = match method {
0 => {
let allocation = allocation_or_empty_info
.as_account_mut::<Allocation>(&airdrop_api::ID)?
.assert_mut(|a| a.campaign == *campaign_info.key)?
.assert_mut(|a| a.recipient == *recipient_info.key)?
.assert_mut(|a| a.claimed == 0)?;
let (expected_allocation, _) = allocation_pda(*campaign_info.key, *recipient_info.key);
allocation_or_empty_info.has_address(&expected_allocation)?;
let claim_amount = allocation.amount;
let clock = Clock::get()?;
allocation.claimed = 1;
allocation.claimed_at = clock.unix_timestamp;
claim_amount
}
1 => {
if amount == 0 {
return Err(AirdropError::InvalidAmount.into());
}
amount
}
2 => {
if campaign.merkle_root == [0u8; 32] {
return Err(AirdropError::MerkleRootNotSet.into());
}
if amount == 0 {
return Err(AirdropError::InvalidAmount.into());
}
let proof = parse_merkle_proof(data, args.merkle_proof_length)?;
if !verify_merkle_proof(&proof, recipient_info.key, amount, campaign.merkle_root) {
return Err(AirdropError::InvalidMerkleProof.into());
}
amount
}
_ => return Err(AirdropError::InvalidAmount.into()),
};
if recipient_tokens_info.data_is_empty() {
create_associated_token_account(
recipient_info,
recipient_info,
recipient_tokens_info,
mint_info,
system_program,
token_program,
associated_token_program,
)?;
}
transfer_signed(
treasury_info,
treasury_tokens_info,
recipient_tokens_info,
token_program,
claim_amount,
&[TREASURY, &campaign_id],
)?;
campaign.total_claimed = campaign
.total_claimed
.checked_add(claim_amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
TokensClaimedEvent {
campaign: *campaign_info.key,
recipient: *recipient_info.key,
amount: claim_amount,
method,
_padding: [0u8; 7],
}
.log();
Ok(())
}
fn parse_merkle_proof(data: &[u8], proof_length: u8) -> Result<MerkleProof, ProgramError> {
if proof_length > MAX_MERKLE_PROOF_LENGTH {
return Err(AirdropError::InvalidMerkleProof.into());
}
let proof_length = proof_length as usize;
let fixed_size = std::mem::size_of::<Claim>();
let variable_data = &data[fixed_size..];
let mut offset = 0;
let proof_path_size = proof_length * 32;
if offset + proof_path_size > variable_data.len() {
return Err(ProgramError::InvalidInstructionData);
}
let mut proof_path = Vec::new();
for i in 0..proof_length {
let start = offset + i * 32;
let end = start + 32;
let hash: [u8; 32] = variable_data[start..end]
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?;
proof_path.push(hash);
}
offset += proof_path_size;
if offset + proof_length > variable_data.len() {
return Err(ProgramError::InvalidInstructionData);
}
let mut proof_indices = Vec::new();
for i in 0..proof_length {
let index_byte = variable_data[offset + i];
proof_indices.push(index_byte != 0);
}
Ok(MerkleProof::new(proof_path, proof_indices))
}