some monster commit here, almost done wiht initial draft

This commit is contained in:
Jordan Prince 2021-09-09 22:16:10 -05:00
parent c2b655e280
commit 374a39b322
2 changed files with 458 additions and 84 deletions

View File

@ -2,18 +2,20 @@ pub mod utils;
use {
crate::utils::{
assert_data_valid, assert_derivation, assert_initialized, assert_owned_by,
assert_valid_amount, create_or_allocate_account_raw, spl_token_transfer,
TokenTransferParams,
adjust_counts, assert_data_valid, assert_derivation, assert_initialized, assert_owned_by,
assert_valid_amount, create_or_allocate_account_raw, get_mask_and_index_for_seq,
spl_token_mint_to, spl_token_transfer, TokenTransferParams,
},
anchor_lang::{
prelude::*,
solana_program::{program_pack::Pack, system_program},
solana_program::{program_pack::Pack, system_program, system_instruction, program::{invoke, invoke_signed}},
AnchorDeserialize, AnchorSerialize,
},
anchor_spl::token::{self, Mint, TokenAccount},
anchor_spl::token::Mint,
spl_token::{instruction::initialize_account2, state::Account},
std::str::FromStr,
std::{str::FromStr, convert::TryFrom},
};
pub const PREFIX: &str = "fair_launch";
@ -25,12 +27,6 @@ pub const MAX_GRANULARITY: u64 = 100;
#[program]
pub mod fair_launch {
use anchor_lang::solana_program::{
program::{invoke, invoke_signed},
system_instruction,
};
use super::*;
pub fn initialize_fair_launch<'info>(
ctx: Context<'_, '_, '_, 'info, InitializeFairLaunch<'info>>,
@ -141,28 +137,15 @@ pub mod fair_launch {
Ok(())
}
pub fn start_phase_three(
ctx: Context<StartPhaseThree>,
phase_three_start: i64,
phase_three_end: i64,
) -> ProgramResult {
pub fn start_phase_three(ctx: Context<StartPhaseThree>) -> ProgramResult {
let fair_launch = &mut ctx.accounts.fair_launch;
let fair_launch_lottery_bitmap = &ctx.accounts.fair_launch_lottery_bitmap;
if fair_launch_lottery_bitmap.bitmap_ones != fair_launch.number_tickets_sold_in_phase_1 {
if fair_launch_lottery_bitmap.bitmap_ones != fair_launch.number_tickets_sold {
return Err(ErrorCode::LotteryBitmapOnesMustEqualNumberOfTicketsSold.into());
}
if phase_three_start < fair_launch.data.phase_two_end {
return Err(ErrorCode::TimestampsDontLineUp.into());
}
if phase_three_end <= phase_three_start {
return Err(ErrorCode::TimestampsDontLineUp.into());
}
fair_launch.data.phase_three_start = Some(phase_three_start);
fair_launch.data.phase_three_end = Some(phase_three_end);
fair_launch.phase_three_started = true;
Ok(())
}
@ -174,10 +157,6 @@ pub mod fair_launch {
) -> ProgramResult {
let fair_launch = &mut ctx.accounts.fair_launch;
if fair_launch.data.phase_three_start.is_some() {
return Err(ErrorCode::CannotUpdateFairLaunchLotteryOncePhaseThreeLocked.into());
}
if fair_launch.number_tickets_un_seqed > 0 {
return Err(ErrorCode::CannotSetFairLaunchLotteryUntilAllTicketsAreSequenced.into());
}
@ -280,14 +259,19 @@ pub mod fair_launch {
fair_launch_ticket.amount = amount;
fair_launch_ticket.state = FairLaunchTicketState::NoSequenceStruct; // Be verbose even though it's 0
fair_launch_ticket.bump = bump;
fair_launch_ticket.seq = fair_launch.number_tickets_sold_in_phase_1;
fair_launch_ticket.seq = fair_launch.number_tickets_sold;
fair_launch.number_tickets_sold_in_phase_1 = fair_launch
.number_tickets_sold_in_phase_1
fair_launch.number_tickets_sold = fair_launch
.number_tickets_sold
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
fair_launch.number_tickets_un_seqed = fair_launch.number_tickets_un_seqed.checked_add(1).ok_or(ErrorCode::NumericalOverflowError)?;
fair_launch.number_tickets_un_seqed = fair_launch
.number_tickets_un_seqed
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
adjust_counts(fair_launch, amount, None)?;
if let Some(treasury_mint) = fair_launch.treasury_mint {
let treasury_mint_info = &ctx.remaining_accounts[0];
@ -357,13 +341,12 @@ pub mod fair_launch {
bump: u8,
) -> ProgramResult {
let fair_launch = &mut ctx.accounts.fair_launch;
let fair_launch_ticket = &mut ctx.accounts.fair_launch_ticket;
let fair_launch_ticket_seq_lookup = &mut ctx.accounts.fair_launch_ticket_seq_lookup;
if fair_launch_ticket.state.clone() as u8 != FairLaunchTicketState::NoSequenceStruct as u8 {
// Due to anchor this should never happen but if it does, i want to be sure.
return Err(ErrorCode::SeqAlreadyExists.into())
return Err(ErrorCode::SeqAlreadyExists.into());
}
// Literally duplicative but I am paranoid of Anchor not doing this right.
assert_derivation(
@ -377,9 +360,258 @@ pub mod fair_launch {
)?;
fair_launch_ticket_seq_lookup.bump = bump;
fair_launch_ticket_seq_lookup.fair_launch_ticket = fair_launch_ticket.key();
fair_launch_ticket_seq_lookup.buyer = fair_launch_ticket.buyer;
fair_launch_ticket.state = FairLaunchTicketState::Unpunched;
fair_launch.number_tickets_un_seqed = fair_launch.number_tickets_un_seqed.checked_sub(1).ok_or(ErrorCode::NumericalOverflowError)?;
fair_launch.number_tickets_un_seqed = fair_launch
.number_tickets_un_seqed
.checked_sub(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
Ok(())
}
pub fn adjust_ticket<'info>(
ctx: Context<'_, '_, '_, 'info, AdjustTicket<'info>>,
amount: u64,
) -> ProgramResult {
let fair_launch = &mut ctx.accounts.fair_launch;
let fair_launch_ticket = &mut ctx.accounts.fair_launch_ticket;
let fair_launch_lottery_bitmap_info = ctx.accounts.fair_launch_lottery_bitmap;
let buyer = &mut ctx.accounts.buyer;
let clock = &mut ctx.accounts.clock;
assert_derivation(
ctx.program_id,
&fair_launch_lottery_bitmap_info,
&[
PREFIX.as_bytes(),
fair_launch.token_mint.as_ref(),
LOTTERY.as_bytes(),
],
)?;
if fair_launch.phase_three_started {
let fair_launch_lottery_bitmap =
FairLaunchLotteryBitmap::try_from(fair_launch_lottery_bitmap_info)?;
if fair_launch_ticket.amount < fair_launch.current_median && amount != 0 {
return Err(ErrorCode::CanOnlySubmitZeroDuringPhaseThree.into());
} else if fair_launch_ticket.amount >= fair_launch.current_median {
let (mask, index) = get_mask_and_index_for_seq(fair_launch_ticket.seq)?;
let is_winner = fair_launch_lottery_bitmap_info.data.borrow()
[FAIR_LAUNCH_LOTTERY_SIZE + index]
& mask;
if is_winner == 1 {
let difference = fair_launch_ticket
.amount
.checked_sub(fair_launch.current_median)
.ok_or(ErrorCode::NumericalOverflowError)?;
if amount != difference {
return Err(ErrorCode::CanOnlySubmitDifferenceDuringPhaseThree.into());
}
} else if amount != 0 {
return Err(ErrorCode::DidNotWinLotteryCanOnlyWithdraw.into());
}
}
} else if !buyer.is_signer {
return Err(ErrorCode::DuringPhaseTwoAndOneBuyerMustBeSigner.into())
}
if amount != 0 {
assert_valid_amount(&fair_launch.data, amount)?;
if fair_launch_ticket.amount == 0 {
// Going from zero to not zero again - subtract from dropped counter
fair_launch.number_tickets_dropped = fair_launch
.number_tickets_dropped
.checked_sub(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
} else if fair_launch_ticket.amount != 0 {
// going from not zero to zero
fair_launch.number_tickets_dropped = fair_launch
.number_tickets_dropped
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
adjust_counts(fair_launch, amount, Some(fair_launch_ticket.amount))?;
let signer_seeds = [
PREFIX.as_bytes(),
fair_launch.token_mint.as_ref(),
&[fair_launch.bump],
];
if let Some(treasury_mint) = fair_launch.treasury_mint {
let treasury_mint_info = &ctx.remaining_accounts[0];
let _treasury_mint: spl_token::state::Mint = assert_initialized(&treasury_mint_info)?;
let buyer_token_account_info = &ctx.remaining_accounts[1];
let buyer_token_account: Account = assert_initialized(&buyer_token_account_info)?;
let transfer_authority_info = &ctx.remaining_accounts[2];
let token_program = &ctx.remaining_accounts[3];
if token_program.key != &spl_token::id() {
return Err(ErrorCode::InvalidTokenProgram.into());
}
if *treasury_mint_info.key != treasury_mint {
return Err(ErrorCode::TreasuryMintMismatch.into());
}
assert_owned_by(treasury_mint_info, &token_program.key)?;
assert_owned_by(buyer_token_account_info, &token_program.key)?;
// assert is an ATA
assert_derivation(
&Pubkey::from_str(ASSOCIATED_TOKEN_PROGRAM_ID).unwrap(),
buyer_token_account_info,
&[
buyer.key.as_ref(),
token_program.key.as_ref(),
&treasury_mint_info.key.as_ref(),
],
)?;
if amount > fair_launch_ticket.amount {
let difference = amount
.checked_sub(fair_launch_ticket.amount)
.ok_or(ErrorCode::NumericalOverflowError)?;
if buyer_token_account.amount < difference {
return Err(ErrorCode::NotEnoughTokens.into());
}
spl_token_transfer(TokenTransferParams {
source: buyer_token_account_info.clone(),
destination: ctx.accounts.treasury.clone(),
authority: transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program.clone(),
amount: difference,
})?;
} else if amount < fair_launch_ticket.amount {
let difference = fair_launch_ticket
.amount
.checked_sub(amount)
.ok_or(ErrorCode::NumericalOverflowError)?;
spl_token_transfer(TokenTransferParams {
source: ctx.accounts.treasury.clone(),
destination: buyer_token_account_info.clone(),
authority: fair_launch.to_account_info(),
authority_signer_seeds: &signer_seeds,
token_program: token_program.clone(),
amount: difference,
})?;
}
} else {
if amount > fair_launch_ticket.amount {
let difference = amount
.checked_sub(fair_launch_ticket.amount)
.ok_or(ErrorCode::NumericalOverflowError)?;
if buyer.lamports() < difference {
return Err(ErrorCode::NotEnoughSOL.into());
}
invoke(
&system_instruction::transfer(buyer.key, ctx.accounts.treasury.key, difference),
&[
buyer.clone(),
ctx.accounts.treasury.clone(),
ctx.accounts.system_program.clone(),
],
)?;
} else if amount < fair_launch_ticket.amount {
let difference = fair_launch_ticket
.amount
.checked_sub(amount)
.ok_or(ErrorCode::NumericalOverflowError)?;
invoke_signed(
&system_instruction::transfer(ctx.accounts.treasury.key, buyer.key, difference),
&[
buyer.clone(),
ctx.accounts.treasury.clone(),
ctx.accounts.system_program.clone(),
],
&[&signer_seeds],
)?;
}
}
fair_launch_ticket.amount = amount;
Ok(())
}
pub fn punch_ticket<'info>(
ctx: Context<'_, '_, '_, 'info, PunchTicket<'info>>,
bump: u8,
) -> ProgramResult {
let fair_launch = &mut ctx.accounts.fair_launch;
let fair_launch_ticket = &mut ctx.accounts.fair_launch_ticket;
let fair_launch_lottery_bitmap = &ctx.accounts.fair_launch_lottery_bitmap;
let buyer_token_account_info = &ctx.accounts.buyer_token_account;
let token_program = &ctx.accounts.token_program;
let token_mint = &ctx.accounts.token_mint;
let (mask, index) = get_mask_and_index_for_seq(fair_launch_ticket.seq)?;
let is_winner = fair_launch_lottery_bitmap.to_account_info().data.borrow()
[FAIR_LAUNCH_LOTTERY_SIZE + index]
& mask;
if is_winner != 1 {
return Err(ErrorCode::DidNotWinLotteryCanOnlyWithdraw.into());
}
// assert is an ATA owned by the buyer on the fair launch ticket, has no delegates, is a token account,
// etc Since this is a permissionless endpoint (for cranks)
assert_derivation(
&Pubkey::from_str(ASSOCIATED_TOKEN_PROGRAM_ID).unwrap(),
buyer_token_account_info,
&[
fair_launch_ticket.buyer.as_ref(),
token_program.key.as_ref(),
&token_mint.key.as_ref(),
],
)?;
let buyer_token: Account = assert_initialized(buyer_token_account_info)?;
assert_owned_by(buyer_token_account_info, token_program.key)?;
if buyer_token.delegate.is_some() {
return Err(ErrorCode::AccountShouldHaveNoDelegates.into());
}
fair_launch.number_tickets_punched = fair_launch
.number_tickets_punched
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
let signer_seeds = [
PREFIX.as_bytes(),
fair_launch.token_mint.as_ref(),
&[fair_launch.bump],
];
spl_token_mint_to(
token_mint.clone(),
buyer_token_account_info.clone(),
1,
fair_launch.to_account_info(),
&signer_seeds,
token_program.clone(),
)?;
Ok(())
}
}
@ -431,7 +663,7 @@ pub struct StartPhaseThree<'info> {
pub struct CreateFairLaunchLotteryBitmap<'info> {
#[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=authority)]
fair_launch: ProgramAccount<'info, FairLaunch>,
#[account(init, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), LOTTERY.as_bytes()], payer=payer, bump=bump, space= FAIR_LAUNCH_LOTTERY_SIZE + (fair_launch.number_tickets_sold_in_phase_1.checked_div(8).ok_or(ErrorCode::NumericalOverflowError)? as usize) + 1)]
#[account(init, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), LOTTERY.as_bytes()], payer=payer, bump=bump, space= FAIR_LAUNCH_LOTTERY_SIZE + (fair_launch.number_tickets_sold.checked_div(8).ok_or(ErrorCode::NumericalOverflowError)? as usize) + 1)]
fair_launch_lottery_bitmap: ProgramAccount<'info, FairLaunchLotteryBitmap>,
#[account(signer)]
authority: AccountInfo<'info>,
@ -501,36 +733,58 @@ pub struct CreateTicketSeq<'info> {
/// In phase 3, if you are above the decided_median, you can only adjust down to decided median. If below, you can only
/// adjust down, never up.
#[derive(Accounts)]
#[instruction(amount: u64)]
pub struct AdjustTicket<'info> {
#[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), buyer.key.as_ref()], bump=fair_launch_ticket.bump,has_one=buyer, has_one=fair_launch)]
fair_launch_ticket: ProgramAccount<'info, FairLaunchTicket>,
#[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump)]
#[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump)]
fair_launch: ProgramAccount<'info, FairLaunch>,
fair_launch_lottery_bitmap: AccountInfo<'info>,
#[account(mut)]
treasury: AccountInfo<'info>,
#[account(mut, signer)]
#[account(mut)]
buyer: AccountInfo<'info>,
#[account(address = system_program::ID)]
system_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
// Remaining accounts in this order if using spl tokens for payment:
// [Writable/optional] treasury mint
// [Writable/optional] buyer token account (must be ata)
// [optional] transfer authority to transfer amount from buyer token account ( may be 0 if transferring money out )
// [optional] token program
}
#[derive(Accounts)]
pub struct PunchTicket<'info> {
#[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), buyer.key.as_ref()], bump=fair_launch_ticket.bump, has_one=buyer, has_one=fair_launch)]
#[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), fair_launch_ticket.buyer.as_ref()], bump=fair_launch_ticket.bump, has_one=fair_launch)]
fair_launch_ticket: ProgramAccount<'info, FairLaunchTicket>,
#[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=token_mint)]
fair_launch: ProgramAccount<'info, FairLaunch>,
#[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), LOTTERY.as_bytes()], bump=fair_launch_lottery_bitmap.bump)]
fair_launch_lottery_bitmap: ProgramAccount<'info, FairLaunchLotteryBitmap>,
#[account(mut, signer)]
buyer: AccountInfo<'info>,
#[account(mut, constraint=&buyer_token_account.mint == token_mint.key && buyer_token_account.to_account_info().owner == &spl_token::id())]
buyer_token_account: CpiAccount<'info, TokenAccount>,
payer: AccountInfo<'info>,
#[account(mut)]
buyer_token_account: AccountInfo<'info>,
#[account(seeds=[PREFIX.as_bytes(), fair_launch.authority.as_ref(), MINT.as_bytes(), fair_launch.data.uuid.as_bytes()], bump=fair_launch.token_mint_bump)]
token_mint: AccountInfo<'info>,
#[account(address = spl_token::id())]
token_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct WithdrawFunds<'info> {
#[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=authority, has_one=treasury)]
fair_launch: ProgramAccount<'info, FairLaunch>,
#[account(mut)]
treasury: AccountInfo<'info>,
#[account(mut, signer)]
authority: AccountInfo<'info>,
// Remaining accounts in this order if using spl tokens for payment:
// [Writable/optional] treasury mint
// [Writable/optional] buyer token account (must be ata)
// [optional] token program
}
pub const FAIR_LAUNCH_LOTTERY_SIZE: usize = 8 + // discriminator
32 + // fair launch
1 + // bump
@ -549,14 +803,13 @@ pub const FAIR_LAUNCH_SPACE_VEC_START: usize = 8 + // discriminator
8 + // phase one start
8 + // phase one end
8 + // phase two end
9 + // phase three start
9 + // phase three end
8 + // tick size
8 + // number of tokens
8 + // number of tickets unseq'ed
8 + // number of tickets sold in phase 1
8 + // number of tickets remaining at the end in phase 2
8 + // number of tickets punched in phase 3
8 + // number of tickets sold
8 + // number of tickets dropped
8 + // number of tickets punched
1 + // phase three started
8 + // current median,
4; // u32 representing number of amounts in vec so far
@ -570,6 +823,7 @@ pub const FAIR_LAUNCH_TICKET_SIZE: usize = 8 + // discriminator
pub const FAIR_LAUNCH_TICKET_SEQ_SIZE: usize = 8 + //discriminator
32 + // fair launch ticket reverse lookup
32 + // buyer,
8 + //seq
1; // bump
@ -581,8 +835,6 @@ pub struct FairLaunchData {
pub phase_one_start: i64,
pub phase_one_end: i64,
pub phase_two_end: i64,
pub phase_three_start: Option<i64>,
pub phase_three_end: Option<i64>,
pub tick_size: u64,
pub number_of_tokens: u64,
}
@ -598,9 +850,10 @@ pub struct FairLaunch {
pub token_mint_bump: u8,
pub data: FairLaunchData,
pub number_tickets_un_seqed: u64,
pub number_tickets_sold_in_phase_1: u64,
pub number_tickets_remaining_in_phase_2: u64,
pub number_tickets_punched_in_phase_3: u64,
pub number_tickets_sold: u64,
pub number_tickets_dropped: u64,
pub number_tickets_punched: u64,
pub phase_three_started: bool,
pub current_median: u64,
pub counts_at_each_tick: Vec<u64>,
}
@ -634,6 +887,7 @@ pub struct FairLaunchTicket {
#[account]
pub struct FairLaunchTicketSeqLookup {
pub fair_launch_ticket: Pubkey,
pub buyer: Pubkey,
pub seq: u64,
pub bump: u8,
}
@ -691,5 +945,17 @@ pub enum ErrorCode {
#[msg("Seq already exists")]
SeqAlreadyExists,
#[msg("Cannot set lottery until all tickets have sequence lookups using permissionless crank endpoint. Use CLI to make.")]
CannotSetFairLaunchLotteryUntilAllTicketsAreSequenced
CannotSetFairLaunchLotteryUntilAllTicketsAreSequenced,
#[msg("During phase three, since you did not pay up to the median, you can only withdraw your funds")]
CanOnlySubmitZeroDuringPhaseThree,
#[msg("During phase three, since you paid above median, you can only withdraw the difference")]
CanOnlySubmitDifferenceDuringPhaseThree,
#[msg("You did not win the lottery, therefore you can only withdraw your funds")]
DidNotWinLotteryCanOnlyWithdraw,
#[msg("This account should have no delegates")]
AccountShouldHaveNoDelegates,
#[msg("Token minting failed")]
TokenMintToFailed,
#[msg("During phase two and one buyer must be signer")]
DuringPhaseTwoAndOneBuyerMustBeSigner
}

View File

@ -1,15 +1,17 @@
use {
crate::{ErrorCode,FairLaunchData,MAX_GRANULARITY},
crate::{ErrorCode, FairLaunch, FairLaunchData, MAX_GRANULARITY},
anchor_lang::{
prelude::{AccountInfo, ProgramError, ProgramResult, Pubkey, Rent, msg, SolanaSysvar},
prelude::{
msg, AccountInfo, ProgramAccount, ProgramError, ProgramResult, Pubkey, Rent,
SolanaSysvar,
},
solana_program::{
program::{invoke_signed, invoke},
system_instruction,
program::{invoke, invoke_signed},
program_pack::{IsInitialized, Pack},
system_instruction,
},
},
std::convert::TryInto
std::convert::TryInto,
};
pub fn assert_initialized<T: Pack + IsInitialized>(
@ -73,58 +75,142 @@ pub fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult
result.map_err(|_| ErrorCode::TokenTransferFailed.into())
}
pub fn adjust_counts(
fair_launch: &mut ProgramAccount<FairLaunch>,
new_amount: u64,
old_amount: Option<u64>,
) -> ProgramResult {
let price_range_offset = fair_launch
.data
.price_range_start
.checked_div(fair_launch.data.tick_size)
.ok_or(ErrorCode::NumericalOverflowError)?;
if let Some(old) = old_amount {
if old >= fair_launch.data.price_range_start {
let mut index = old
.checked_div(fair_launch.data.tick_size)
.ok_or(ErrorCode::NumericalOverflowError)?;
index = index
.checked_sub(price_range_offset)
.ok_or(ErrorCode::NumericalOverflowError)?;
let place = index as usize;
fair_launch.counts_at_each_tick[place] = fair_launch.counts_at_each_tick[place]
.checked_sub(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
}
if new_amount >= fair_launch.data.price_range_start {
let mut index = new_amount
.checked_div(fair_launch.data.tick_size)
.ok_or(ErrorCode::NumericalOverflowError)?;
index = index
.checked_sub(price_range_offset)
.ok_or(ErrorCode::NumericalOverflowError)?;
let place = index as usize;
fair_launch.counts_at_each_tick[place] = fair_launch.counts_at_each_tick[place]
.checked_add(1)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
let mut total_counts: u64 = 0;
for n in &fair_launch.counts_at_each_tick {
total_counts = total_counts
.checked_add(*n)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
let median_location = total_counts
.checked_div(2)
.ok_or(ErrorCode::NumericalOverflowError)?;
let mut counter: u64 = 0;
let mut ticks: u64 = 0;
for n in &fair_launch.counts_at_each_tick {
counter = counter
.checked_add(*n)
.ok_or(ErrorCode::NumericalOverflowError)?;
if counter > median_location {
break;
}
ticks = ticks
.checked_add(fair_launch.data.tick_size)
.ok_or(ErrorCode::NumericalOverflowError)?;
}
fair_launch.current_median = ticks
.checked_add(fair_launch.data.price_range_start)
.ok_or(ErrorCode::NumericalOverflowError)?;
Ok(())
}
pub fn get_mask_and_index_for_seq(seq: u64) -> Result<(u8, usize), ProgramError> {
let my_position_in_index = seq
.checked_div(8)
.ok_or(ErrorCode::NumericalOverflowError)?;
let my_position_from_right = 7 - seq
.checked_div(8)
.ok_or(ErrorCode::NumericalOverflowError)?;
let mask = u8::pow(2, my_position_from_right as u32);
Ok((mask, my_position_in_index as usize))
}
pub fn assert_data_valid(data: &FairLaunchData) -> ProgramResult {
if data.phase_one_end < data.phase_one_start {
return Err(ErrorCode::TimestampsDontLineUp.into())
return Err(ErrorCode::TimestampsDontLineUp.into());
}
if data.phase_two_end < data.phase_one_end {
return Err(ErrorCode::TimestampsDontLineUp.into())
}
if data.phase_three_start.is_some() || data.phase_three_end.is_some() {
return Err(ErrorCode::CantSetPhaseThreeDatesYet.into())
return Err(ErrorCode::TimestampsDontLineUp.into());
}
if data.uuid.len() != 6 {
return Err(ErrorCode::UuidMustBeExactly6Length.into())
return Err(ErrorCode::UuidMustBeExactly6Length.into());
}
if data.tick_size == 0 {
return Err(ErrorCode::TickSizeTooSmall.into())
return Err(ErrorCode::TickSizeTooSmall.into());
}
if data.number_of_tokens == 0 {
return Err(ErrorCode::CannotGiveZeroTokens.into())
return Err(ErrorCode::CannotGiveZeroTokens.into());
}
if data.price_range_end <= data.price_range_start {
return Err(ErrorCode::InvalidPriceRanges.into())
return Err(ErrorCode::InvalidPriceRanges.into());
}
let difference = data.price_range_start.checked_sub(data.price_range_end).ok_or(ErrorCode::NumericalOverflowError)?;
let possible_valid_user_prices = difference.checked_div(data.tick_size).ok_or(ErrorCode::NumericalOverflowError)?;
let remainder = difference.checked_rem(data.tick_size).ok_or(ErrorCode::NumericalOverflowError)?;
let difference = data
.price_range_start
.checked_sub(data.price_range_end)
.ok_or(ErrorCode::NumericalOverflowError)?;
let possible_valid_user_prices = difference
.checked_div(data.tick_size)
.ok_or(ErrorCode::NumericalOverflowError)?;
let remainder = difference
.checked_rem(data.tick_size)
.ok_or(ErrorCode::NumericalOverflowError)?;
if remainder > 0 {
return Err(ErrorCode::CannotUseTickSizeThatGivesRemainder.into())
return Err(ErrorCode::CannotUseTickSizeThatGivesRemainder.into());
}
if possible_valid_user_prices > MAX_GRANULARITY {
return Err(ErrorCode::TooMuchGranularityInRange.into())
return Err(ErrorCode::TooMuchGranularityInRange.into());
}
Ok(())
}
pub fn assert_valid_amount(data: &FairLaunchData, amount: u64) -> ProgramResult {
if amount < data.price_range_start || amount > data.price_range_end {
return Err(ErrorCode::InvalidPurchaseAmount.into())
return Err(ErrorCode::InvalidPurchaseAmount.into());
}
if amount.checked_rem(data.tick_size).is_some() {
return Err(ErrorCode::InvalidPurchaseAmount.into())
return Err(ErrorCode::InvalidPurchaseAmount.into());
}
Ok(())
@ -142,7 +228,6 @@ pub fn assert_derivation(
Ok(bump)
}
/// Create account almost from scratch, lifted from
/// https://github.com/solana-labs/solana-program-library/blob/7d4873c61721aca25464d42cc5ef651a7923ca79/associated-token-account/program/src/processor.rs#L51-L98
#[inline(always)]
@ -192,3 +277,26 @@ pub fn create_or_allocate_account_raw<'a>(
Ok(())
}
pub fn spl_token_mint_to<'a: 'b, 'b>(
mint: AccountInfo<'a>,
destination: AccountInfo<'a>,
amount: u64,
authority: AccountInfo<'a>,
authority_signer_seeds: &'b [&'b [u8]],
token_program: AccountInfo<'a>,
) -> ProgramResult {
let result = invoke_signed(
&spl_token::instruction::mint_to(
token_program.key,
mint.key,
destination.key,
authority.key,
&[],
amount,
)?,
&[mint, destination, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| ErrorCode::TokenMintToFailed.into())
}