use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
system_program,
};
use crate::state:: CentralStateV2;
use crate::error::AccessError;
use crate::state::{BondAccount, StakePool, BOND_SIGNER_THRESHOLD, V1_INSTRUCTIONS_ALLOWED};
#[cfg(not(feature = "no-bond-signer"))]
use crate::utils::assert_authorized_seller;
use crate::utils::{assert_uninitialized, check_account_key, check_account_owner, check_signer};
use crate::{cpi::Cpi, state::Tag};
use bonfida_utils::{BorshSize, InstructionsAccount};
use crate::instruction::ProgramInstruction::CreateBond;
#[derive(BorshDeserialize, BorshSerialize, BorshSize)]
pub struct Params {
pub buyer: Pubkey,
pub total_amount_sold: u64,
pub total_quote_amount: u64,
pub quote_mint: Pubkey,
pub seller_token_account: Pubkey,
pub unlock_start_date: i64,
pub unlock_period: i64,
pub unlock_amount: u64,
pub seller_index: u64,
}
#[derive(InstructionsAccount)]
pub struct Accounts<'a, T> {
#[cons(writable, signer)]
pub seller: &'a T,
#[cons(writable)]
pub bond_account: &'a T,
pub stake_pool: &'a T,
pub system_program: &'a T,
#[cons(writable, signer)]
pub fee_payer: &'a T,
pub central_state: &'a T,
}
impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
pub fn parse(
accounts: &'a [AccountInfo<'b>],
program_id: &Pubkey,
) -> Result<Self, ProgramError> {
let accounts_iter = &mut accounts.iter();
let accounts = Accounts {
seller: next_account_info(accounts_iter)?,
bond_account: next_account_info(accounts_iter)?,
stake_pool: next_account_info(accounts_iter)?,
system_program: next_account_info(accounts_iter)?,
fee_payer: next_account_info(accounts_iter)?,
central_state: next_account_info(accounts_iter)?,
};
check_account_key(
accounts.system_program,
&system_program::ID,
AccessError::WrongSystemProgram,
)?;
check_account_owner(accounts.central_state, program_id, AccessError::WrongOwner)?;
check_account_owner(accounts.stake_pool, program_id, AccessError::WrongOwner)?;
check_signer(accounts.seller, AccessError::BondSellerMustSign)?;
Ok(accounts)
}
}
pub fn process_create_bond(
program_id: &Pubkey,
accounts: &[AccountInfo],
params: Params,
) -> ProgramResult {
if !V1_INSTRUCTIONS_ALLOWED {
return Err(AccessError::DeprecatedInstruction.into());
}
let accounts = Accounts::parse(accounts, program_id)?;
let central_state = CentralStateV2::from_account_info(accounts.central_state)?;
central_state.assert_instruction_allowed(&CreateBond)?;
let (derived_key, nonce) =
BondAccount::create_key(¶ms.buyer, params.total_amount_sold, program_id);
let stake_pool = StakePool::get_checked(accounts.stake_pool, vec![Tag::StakePool])?;
check_account_key(
accounts.bond_account,
&derived_key,
AccessError::AccountNotDeterministic,
)?;
assert_uninitialized(accounts.bond_account)?;
#[cfg(not(feature = "no-bond-signer"))]
assert_authorized_seller(accounts.seller, params.seller_index as usize)?;
if params.unlock_period == 0 {
return Err(AccessError::ForbiddenUnlockPeriodZero.into());
}
let bond = BondAccount::new(
params.buyer,
params.total_amount_sold,
params.total_quote_amount,
params.quote_mint,
params.seller_token_account,
params.unlock_start_date,
params.unlock_period,
params.unlock_amount,
params.unlock_start_date,
stake_pool.header.minimum_stake_amount,
*accounts.stake_pool.key,
*accounts.seller.key,
);
let seeds: &[&[u8]] = &[
BondAccount::SEED,
¶ms.buyer.to_bytes(),
¶ms.total_amount_sold.to_le_bytes(),
&[nonce],
];
Cpi::create_account(
program_id,
accounts.system_program,
accounts.fee_payer,
accounts.bond_account,
seeds,
bond.borsh_len() + ((BOND_SIGNER_THRESHOLD - 1) * 32) as usize,
)?;
bond.save(&mut accounts.bond_account.data.borrow_mut())?;
Ok(())
}