Conclusion
If you've been following along, your create.rs
file is in its final form and should look like this:
//! Create a new token vesting contract
use bonfida_utils::{checks::check_account_owner, WrappedPod};
use solana_program::{msg, program::invoke, program_pack::Pack};
use spl_token::state::AccountState;
use crate::{
error::TokenVestingError,
state::{
self,
vesting_contract::{VestingContract, VestingContractHeader, VestingSchedule},
},
};
use {
bonfida_utils::{
checks::{check_account_key, check_signer},
InstructionsAccount,
},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
},
};
#[derive(WrappedPod)]
pub struct Params<'a> {
signer_nonce: &'a u64,
schedule: &'a [VestingSchedule],
}
#[derive(InstructionsAccount)]
pub struct Accounts<'a, T> {
/// SPL token program account
pub spl_token_program: &'a T,
/// The account which will store the [`VestingContract`] data structure
#[cons(writable)]
pub vesting_contract: &'a T,
/// The contract's escrow vault
#[cons(writable)]
pub vault: &'a T,
#[cons(writable)]
/// The account currently holding the tokens to be vested
pub source_tokens: &'a T,
#[cons(signer)]
/// The owner of the account currently holding the tokens to be vested
pub source_tokens_owner: &'a T,
/// The eventual recipient of the vested tokens
pub recipient: &'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 {
spl_token_program: next_account_info(accounts_iter)?,
vesting_contract: next_account_info(accounts_iter)?,
vault: next_account_info(accounts_iter)?,
source_tokens: next_account_info(accounts_iter)?,
source_tokens_owner: next_account_info(accounts_iter)?,
recipient: next_account_info(accounts_iter)?,
};
// Check keys
check_account_key(accounts.spl_token_program, &spl_token::ID)?;
// Check owners
check_account_owner(accounts.vesting_contract, program_id)?;
check_account_owner(accounts.vault, &spl_token::ID)?;
// Check signer
check_signer(accounts.source_tokens_owner)?;
Ok(accounts)
}
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], params: Params) -> ProgramResult {
let accounts = Accounts::parse(accounts, program_id)?;
let Params {
signer_nonce,
schedule,
} = params;
// We only want a one-byte signer nonce
let signer_nonce = *signer_nonce as u8;
let expected_vesting_contract_account_size =
VestingContract::compute_allocation_size(schedule.len());
if accounts.vesting_contract.data_len() != expected_vesting_contract_account_size {
msg!("The vesting contract account is incorrectly sized for the supplied schedule!");
return Err(ProgramError::InvalidArgument);
}
check_vault_account(
accounts.vault,
program_id,
*accounts.vesting_contract.key,
signer_nonce,
)?;
let mut vesting_contract_guard = accounts.vesting_contract.data.borrow_mut();
VestingContract::initialize(&mut vesting_contract_guard)?;
let vesting_contract =
VestingContract::from_buffer(&mut vesting_contract_guard, state::Tag::VestingContract)?;
*vesting_contract.header = VestingContractHeader {
owner: *accounts.recipient.key,
vault: *accounts.vault.key,
current_schedule_index: 0,
signer_nonce,
_padding: [0; 7],
};
let mut total_amount = 0u64;
for (schedule, slot) in schedule.iter().zip(vesting_contract.schedules.iter_mut()) {
*slot = *schedule;
total_amount = total_amount.checked_add(schedule.quantity).unwrap();
}
let instruction = spl_token::instruction::transfer(
&spl_token::ID,
accounts.source_tokens.key,
accounts.vault.key,
accounts.source_tokens_owner.key,
&[],
total_amount,
)?;
invoke(
&instruction,
&[
accounts.spl_token_program.clone(),
accounts.source_tokens.clone(),
accounts.vault.clone(),
accounts.source_tokens_owner.clone(),
],
)?;
Ok(())
}
fn check_vault_account(
vault: &AccountInfo,
program_id: &Pubkey,
contract_key: Pubkey,
signer_nonce: u8,
) -> Result<(), ProgramError> {
let vault_account = spl_token::state::Account::unpack(&vault.data.borrow())?;
let vault_signer =
Pubkey::create_program_address(&[&contract_key.to_bytes(), &[signer_nonce]], program_id)?;
let is_valid = vault_account.owner == vault_signer
&& vault_account.amount == 0
&& vault_account.delegate.is_none()
&& vault_account.state == AccountState::Initialized
&& vault_account.close_authority.is_none();
if !is_valid {
return Err(TokenVestingError::InvalidVaultAccount.into());
}
Ok(())
}