use solana_program::program_pack::IsInitialized;
use {
crate::{
instruction::{CollectionInstruction, CreateCollectionAccountArgs},
utils::{create_new_account, create_or_allocate_account_raw, get_index_account, get_treasury_account},
state::{PREFIX, AccountType, CollectionAccountData, CollectionIndexAccountData},
error::CollectionError,
check_id,
},
solana_program::{
account_info::{AccountInfo, next_account_info},
system_instruction::transfer,
entrypoint::ProgramResult,
pubkey::Pubkey,
program_pack::Pack,
native_token::sol_to_lamports,
program::invoke,
program_option::COption,
msg,
},
borsh::{BorshDeserialize, BorshSerialize},
spl_token::state::Mint as spl_mint,
spl_token::state::Account as spl_account,
};
use std::str::FromStr;
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
let instruction = CollectionInstruction::try_from_slice(input)?;
match instruction {
CollectionInstruction::CreateCollectionAccount(args) => {
msg!("Instruction: Create Collection Account");
process_create_collection_account(program_id, accounts, &args)
},
CollectionInstruction::IncludeToken => {
msg!("Instruction: Include Token");
process_include_token(program_id, accounts)
},
CollectionInstruction::LightUpStarsOnce => {
msg!("Instruction: Light Up Stars Once");
process_light_up_stars_once(program_id, accounts)
},
CollectionInstruction::LightUpStarsThousand => {
msg!("Instruction: Light Up Stars One Thousand");
process_light_up_stars_thousand(program_id, accounts)
},
CollectionInstruction::LightUpStarsHundred => {
msg!("Instruction: Light Up Stars One Hundred");
process_light_up_stars_hundred(program_id, accounts)
},
CollectionInstruction::CloseAccount(account_type) => {
msg!("Instruction: Close Account");
process_close_account(program_id, accounts, account_type)
},
CollectionInstruction::Withdraw => {
msg!("Instruction: Withdraw");
process_withdraw(program_id, accounts)
}
}
}
pub fn process_create_collection_account(
program_id: &Pubkey,
accounts: &[AccountInfo],
args: &CreateCollectionAccountArgs,
) -> ProgramResult {
assert_program_id(program_id)?;
assert_create_collection_args(args)?;
let account_info_iter = &mut accounts.iter();
let collection_account_info = next_account_info(account_info_iter)?;
let form_account_info = next_account_info(account_info_iter)?;
let rent_account_info = next_account_info(account_info_iter)?;
let collection_account_data = CollectionAccountData {
account_type: AccountType::CollectionAccount,
title: args.title.clone(),
symbol: args.symbol.clone(),
description: args.description.clone(),
icon_image: args.icon_image.clone(),
stars: 0 as u64,
supply: 0 as u64,
authority: *form_account_info.key,
header_image: args.header_image.clone(),
short_description: args.short_description.clone(),
banner: args.banner.clone(),
tags: args.tags.clone(),
};
let mut data: Vec<u8> = Vec::new();
collection_account_data.serialize(&mut data)?;
create_new_account(
form_account_info,
collection_account_info,
data.len(),
program_id,
rent_account_info,
).unwrap();
collection_account_data.serialize(&mut *collection_account_info.data.borrow_mut())?;
Ok(())
}
pub fn process_include_token(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
assert_program_id(program_id)?;
let account_info_iter = &mut accounts.iter();
let collection_account_info = next_account_info(account_info_iter)?;
let collection_auth_account_info = next_account_info(account_info_iter)?;
let mint_account_info = next_account_info(account_info_iter)?;
let mint_token_account_info = next_account_info(account_info_iter)?;
let index_account_info = next_account_info(account_info_iter)?;
let payer_account_info = next_account_info(account_info_iter)?;
let rent_sysvar_info = next_account_info(account_info_iter)?;
let system_program_info = next_account_info(account_info_iter)?;
let mut collection_account_data = CollectionAccountData::try_from_slice_unchecked(
&collection_account_info.data.borrow_mut())?;
if !collection_account_data.is_initialized() {
return Err(CollectionError::Uninitialized.into());
}
if collection_account_data.authority != *collection_auth_account_info.key
|| !collection_auth_account_info.is_signer {
return Err(CollectionError::NotCollectionAuthority.into());
}
assert_mint_authority(
mint_account_info,
mint_token_account_info,
collection_auth_account_info,
)?;
let (index_account, bump_seed) = get_index_account(
mint_account_info.key,
);
if index_account != *index_account_info.key {
return Err(CollectionError::CollectionIndexAccountMismatch.into());
}
let signer_seeds = &[
PREFIX.as_bytes(),
program_id.as_ref(),
mint_account_info.key.as_ref(),
&[bump_seed],
];
create_or_allocate_account_raw(
*program_id,
index_account_info,
rent_sysvar_info,
system_program_info,
payer_account_info,
CollectionIndexAccountData::LEN,
signer_seeds,
)?;
let index_account_data = CollectionIndexAccountData::new(
*collection_account_info.key,
*mint_account_info.key,
collection_account_data.supply,
);
index_account_data.serialize(&mut *index_account_info.data.borrow_mut())?;
collection_account_data.supply += 1;
collection_account_data.serialize(&mut *collection_account_info.data.borrow_mut())?;
Ok(())
}
pub fn process_light_up_stars_once(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
assert_program_id(program_id)?;
let account_info_iter = &mut accounts.iter();
let collection_account_info = next_account_info(account_info_iter)?;
let mut collection_account_data = CollectionAccountData::try_from_slice_unchecked(
&collection_account_info.data.borrow_mut())?;
if !collection_account_data.is_initialized() {
return Err(CollectionError::Uninitialized.into());
}
collection_account_data.stars += 1;
collection_account_data.serialize(&mut *collection_account_info.data.borrow_mut())?;
Ok(())
}
pub fn process_light_up_stars_hundred(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
assert_program_id(program_id)?;
let account_info_iter = &mut accounts.iter();
let collection_account_info = next_account_info(account_info_iter)?;
let source_account_info = next_account_info(account_info_iter)?;
let destination_account_info = next_account_info(account_info_iter)?;
assert_treasury_account(destination_account_info)?;
let lamports = sol_to_lamports(0.01);
invoke(
&transfer(
source_account_info.key,
destination_account_info.key,
lamports,
),
&[
source_account_info.clone(),
destination_account_info.clone(),
],
)?;
let mut collection_account_data = CollectionAccountData::try_from_slice_unchecked(
&collection_account_info.data.borrow_mut())?;
if !collection_account_data.is_initialized() {
return Err(CollectionError::Uninitialized.into());
}
collection_account_data.stars += 100;
collection_account_data.serialize(&mut *collection_account_info.data.borrow_mut())?;
Ok(())
}
pub fn process_light_up_stars_thousand(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
assert_program_id(program_id)?;
let account_info_iter = &mut accounts.iter();
let collection_account_info = next_account_info(account_info_iter)?;
let source_account_info = next_account_info(account_info_iter)?;
let destination_account_info = next_account_info(account_info_iter)?;
assert_treasury_account(destination_account_info)?;
let lamports = sol_to_lamports(1 as f64);
invoke(
&transfer(
source_account_info.key,
destination_account_info.key,
lamports,
),
&[
source_account_info.clone(),
destination_account_info.clone(),
],
)?;
let mut collection_account_data = CollectionAccountData::try_from_slice_unchecked(
&collection_account_info.data.borrow_mut())?;
if !collection_account_data.is_initialized() {
return Err(CollectionError::Uninitialized.into());
}
collection_account_data.stars += 1000;
collection_account_data.serialize(&mut *collection_account_info.data.borrow_mut())?;
Ok(())
}
pub fn process_withdraw(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
assert_program_id(program_id)?;
let account_info_iter = &mut accounts.iter();
let treasury_manager_account_info = next_account_info(account_info_iter)?;
let treasury_account_info = next_account_info(account_info_iter)?;
let recipient_account_info = next_account_info(account_info_iter)?;
assert_treasury_manager(treasury_manager_account_info)?;
assert_treasury_account(treasury_account_info)?;
let lamports = treasury_account_info.lamports();
if lamports == 0 {
return Err(CollectionError::InsufficientFunds.into());
}
let recipient_starting_lamports = recipient_account_info.lamports();
**recipient_account_info.lamports.borrow_mut() = recipient_starting_lamports.checked_add(treasury_account_info.lamports()).unwrap();
**treasury_account_info.lamports.borrow_mut() = 0;
Ok(())
}
pub fn process_close_account(
program_id: &Pubkey,
accounts: &[AccountInfo],
account_type: AccountType,
) -> ProgramResult {
assert_program_id(program_id)?;
let account_info_iter = &mut accounts.iter();
let account_info = next_account_info(account_info_iter)?;
let recipient_account_info = next_account_info(account_info_iter)?;
let authority_account_info = next_account_info(account_info_iter)?;
match account_type {
AccountType::Uninitialized => {
return Err(CollectionError::InvalidAccountType.into());
},
AccountType::CollectionAccount => {
msg!("close collection account: {}", account_info.key.to_string());
let collection_data = CollectionAccountData::try_from_slice_unchecked(&account_info.data.borrow_mut())?;
if collection_data.authority != *authority_account_info.key
|| !authority_account_info.is_signer {
return Err(CollectionError::NotCollectionAuthority.into());
}
let recipient_starting_lamports = recipient_account_info.lamports();
**recipient_account_info.lamports.borrow_mut() = recipient_starting_lamports.checked_add(account_info.lamports()).unwrap();
**account_info.lamports.borrow_mut() = 0;
let mut account_data = account_info.data.borrow_mut();
account_data.fill(0);
},
AccountType::CollectionIndexAccount => {
CollectionIndexAccountData::try_from_slice_unchecked(&account_info.data.borrow_mut())?;
let recipient_starting_lamports = recipient_account_info.lamports();
**recipient_account_info.lamports.borrow_mut() = recipient_starting_lamports.checked_add(account_info.lamports()).unwrap();
**account_info.lamports.borrow_mut() = 0;
let mut account_data = account_info.data.borrow_mut();
account_data.fill(0);
}
}
Ok(())
}
fn assert_mint_authority(
mint_account_info: &AccountInfo,
mint_token_account: &AccountInfo,
collection_auth_account_info: &AccountInfo,
) -> ProgramResult {
if *mint_account_info.owner != spl_token::id()
|| *mint_token_account.owner != spl_token::id() {
return Err(CollectionError::InvalidNFT.into());
}
let mint = spl_mint::unpack_unchecked(&mint_account_info.data.borrow())?;
let token_account = spl_account::unpack_unchecked(&mint_token_account.data.borrow())?;
if !mint.is_initialized()
|| mint.supply != 1
|| mint.decimals != 0
|| !token_account.is_initialized()
|| token_account.mint != *mint_account_info.key
|| token_account.amount != 1 {
return Err(CollectionError::InvalidNFT.into());
}
if (token_account.owner == *collection_auth_account_info.key)
|| (token_account.delegate.is_some() && token_account.delegate == COption::Some(*collection_auth_account_info.key) && token_account.delegated_amount == 1) {
return Ok(());
}
return Err(CollectionError::NotCollectionAuthority.into());
}
fn assert_program_id(program_id: &Pubkey) -> ProgramResult {
if !check_id(program_id) {
return Err(CollectionError::InvalidProgramId.into());
}
Ok(())
}
fn assert_create_collection_args(args: &CreateCollectionAccountArgs) -> ProgramResult {
if !args.is_valid() {
return Err(CollectionError::InvalidInstructionArguments.into());
}
Ok(())
}
fn assert_treasury_account(treasury_account_info: &AccountInfo) -> ProgramResult {
let (pda, _) = get_treasury_account();
if *treasury_account_info.key != pda {
return Err(CollectionError::InvalidTreasuryAccount.into());
}
Ok(())
}
fn get_treasury_manager_account() -> Pubkey {
Pubkey::from_str(&"Ep1P3v2rMZ2FkyPx5uuGMaTztdSdtdvaUjcahT9y3EQv".to_string()).unwrap()
}
fn assert_treasury_manager(manager_account_info: &AccountInfo) -> ProgramResult {
if *manager_account_info.key != get_treasury_manager_account()
|| !manager_account_info.is_signer {
return Err(CollectionError::NotTreasuryManager.into());
}
Ok(())
}