From 374a39b322eb7af8f4f10567f32fba604be640c9 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Thu, 9 Sep 2021 22:16:10 -0500 Subject: [PATCH] some monster commit here, almost done wiht initial draft --- rust/fair-launch/src/lib.rs | 384 ++++++++++++++++++++++++++++------ rust/fair-launch/src/utils.rs | 158 +++++++++++--- 2 files changed, 458 insertions(+), 84 deletions(-) diff --git a/rust/fair-launch/src/lib.rs b/rust/fair-launch/src/lib.rs index a8bbb2c..2c96b2e 100644 --- a/rust/fair-launch/src/lib.rs +++ b/rust/fair-launch/src/lib.rs @@ -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, - phase_three_start: i64, - phase_three_end: i64, - ) -> ProgramResult { + pub fn start_phase_three(ctx: Context) -> 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, - pub phase_three_end: Option, 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, } @@ -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 } diff --git a/rust/fair-launch/src/utils.rs b/rust/fair-launch/src/utils.rs index 891a4bb..d4f7d8e 100644 --- a/rust/fair-launch/src/utils.rs +++ b/rust/fair-launch/src/utils.rs @@ -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( @@ -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, + new_amount: u64, + old_amount: Option, +) -> 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()) +} \ No newline at end of file