From 6d323f8baf683f79b3ad2e93b07fbc577e15342a Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 6 Sep 2021 14:56:33 -0500 Subject: [PATCH] Working on fair launch --- rust/fair-launch/src/lib.rs | 93 +++++++++++++++++++++------ rust/fair-launch/src/utils.rs | 116 +++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 22 deletions(-) diff --git a/rust/fair-launch/src/lib.rs b/rust/fair-launch/src/lib.rs index ea225e6..9c588e4 100644 --- a/rust/fair-launch/src/lib.rs +++ b/rust/fair-launch/src/lib.rs @@ -1,25 +1,60 @@ pub mod utils; use { - crate::utils::{assert_initialized, assert_owned_by, spl_token_transfer, TokenTransferParams}, + crate::utils::{assert_initialized, assert_owned_by, spl_token_transfer, TokenTransferParams, assert_data_valid, assert_derivation}, anchor_lang::{ prelude::*, solana_program::{clock::UnixTimestamp, program_pack::Pack, system_program}, AnchorDeserialize, AnchorSerialize, }, - anchor_spl::token::{self, TokenAccount}, - spl_token::state::{Account, Mint}, + anchor_spl::token::{self, TokenAccount, Mint}, + spl_token::state::Account, }; pub const PREFIX: &str = "fair_launch"; pub const TREASURY: &str = "treasury"; pub const MINT: &str = "mint"; pub const LOTTERY: &str="lottery"; +pub const MAX_GRANULARITY:u64 = 100; #[program] pub mod fair_launch { use super::*; - pub fn initialize(ctx: Context) -> ProgramResult { + pub fn initialize_fair_launch(ctx: Context, bump: u8, treasury_bump: u8, token_mint_bump: u8, data: FairLaunchData) -> ProgramResult { + let fair_launch = &mut ctx.accounts.fair_launch; + + assert_data_valid(&data)?; + fair_launch.data = data; + fair_launch.authority = *ctx.accounts.authority.key; + fair_launch.bump = bump; + fair_launch.treasury_bump = treasury_bump; + fair_launch.token_mint_bump = token_mint_bump; + + fair_launch.token_mint = ctx.accounts.token_mint.key(); + assert_owned_by(&ctx.accounts.token_mint.to_account_info(), &spl_token::id())?; //paranoia + + let token_mint_key = ctx.accounts.token_mint.key(); + let treasury_seeds = &[PREFIX.as_bytes(), token_mint_key.as_ref(), TREASURY.as_bytes()]; + let treasury_info = &ctx.accounts.treasury; + fair_launch.treasury = *treasury_info.key; + assert_derivation(ctx.program_id, treasury_info, treasury_seeds)?; + + if ctx.remaining_accounts.len() > 0 { + let treasury_mint_info = &ctx.remaining_accounts[0]; + let _treasury_mint: spl_token::state::Mint = assert_initialized(&treasury_mint_info)?; + + assert_owned_by(&treasury_mint_info, &spl_token::id())?; + + fair_launch.treasury_mint = Some(*treasury_mint_info.key); + + // make the treasury token account + } else { + // Nothing to do but check that it does not already exist, we can begin transferring sol to it. + if !treasury_info.data_is_empty() || treasury_info.lamports() > 0 || treasury_info.owner != ctx.program_id { + return Err(ErrorCode::TreasuryAlreadyExists.into()) + } + } + Ok(()) } } @@ -29,9 +64,8 @@ pub mod fair_launch { pub struct InitializeFairLaunch<'info> { #[account(init, seeds=[PREFIX.as_bytes(), token_mint.key.as_ref()], payer=payer, bump=bump, space=FAIR_LAUNCH_SPACE_VEC_START+16*(((data.price_range_end - data.price_range_start).checked_div(data.tick_size).ok_or(ErrorCode::NumericalOverflowError)? + 1)) as usize)] fair_launch: ProgramAccount<'info, FairLaunch>, - #[account(init, seeds=[PREFIX.as_bytes(), authority.key.as_ref(), MINT.as_bytes(), data.uuid.as_bytes()], payer=payer, bump=token_mint_bump, space=Mint::LEN)] - token_mint: AccountInfo<'info>, - #[account(init, seeds=[PREFIX.as_bytes(), token_mint.key.as_ref(), TREASURY.as_bytes()], payer=payer, bump=treasury_bump, space=Account::LEN)] + #[account(init, seeds=[PREFIX.as_bytes(), authority.key.as_ref(), MINT.as_bytes(), data.uuid.as_bytes()], mint::authority=fair_launch, mint::decimals=0, payer=payer, bump=token_mint_bump)] + token_mint: CpiAccount<'info, Mint>, treasury: AccountInfo<'info>, #[account(constraint= authority.data_is_empty() && authority.lamports() > 0)] authority: AccountInfo<'info>, @@ -47,7 +81,7 @@ pub struct InitializeFairLaunch<'info> { /// Can only update fair launch before phase 1 start. #[derive(Accounts)] pub struct UpdateFairLaunch<'info> { - #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), &[fair_launch.bump]], has_one=authority)] + #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=authority)] fair_launch: ProgramAccount<'info, FairLaunch>, #[account(signer)] authority: AccountInfo<'info>, @@ -56,7 +90,7 @@ pub struct UpdateFairLaunch<'info> { /// Limited Update that only sets phase 3 dates once bitmap is in place. #[derive(Accounts)] pub struct StartPhaseThree<'info> { - #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), &[fair_launch.bump]], has_one=authority)] + #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump,has_one=authority)] fair_launch: ProgramAccount<'info, FairLaunch>, #[account(signer)] authority: AccountInfo<'info>, @@ -66,7 +100,7 @@ pub struct StartPhaseThree<'info> { #[derive(Accounts)] #[instruction(bump: u8)] pub struct CreateFairLaunchLotteryBitmap<'info> { - #[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), &[fair_launch.bump]], has_one=authority)] + #[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)] fair_launch_lottery_bitmap: ProgramAccount<'info, FairLaunchLotteryBitmap>, @@ -82,9 +116,9 @@ pub struct CreateFairLaunchLotteryBitmap<'info> { /// Can only set the fair launch lottery bitmap after phase 2 has ended. #[derive(Accounts)] pub struct UpdateFairLaunchLotteryBitmap<'info> { - #[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), &[fair_launch.bump]], has_one=authority)] + #[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=authority)] fair_launch: ProgramAccount<'info, FairLaunch>, - #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), LOTTERY.as_bytes(), &[fair_launch_lottery_bitmap.bump]])] + #[account(mut, 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(signer)] authority: AccountInfo<'info>, @@ -94,7 +128,7 @@ pub struct UpdateFairLaunchLotteryBitmap<'info> { #[derive(Accounts)] #[instruction(bump: u8, amount: u64)] pub struct PurchaseTicket<'info> { - #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), &[fair_launch.bump]], has_one=treasury)] + #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump, has_one=treasury)] fair_launch: ProgramAccount<'info, FairLaunch>, #[account(mut)] treasury: AccountInfo<'info>, @@ -121,9 +155,9 @@ pub struct PurchaseTicket<'info> { #[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(), &[fair_launch_ticket.bump]], has_one=buyer, has_one=fair_launch)] + #[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(), &[fair_launch.bump]])] + #[account(seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref()], bump=fair_launch.bump)] fair_launch: ProgramAccount<'info, FairLaunch>, #[account(mut)] treasury: AccountInfo<'info>, @@ -136,15 +170,15 @@ pub struct AdjustTicket<'info> { } #[derive(Accounts)] pub struct PunchTicket<'info> { - #[account(mut, seeds=[PREFIX.as_bytes(), fair_launch.token_mint.as_ref(), buyer.key.as_ref(), &[fair_launch_ticket.bump]], has_one=buyer, has_one=fair_launch)] + #[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(), &[fair_launch.bump]], has_one=token_mint)] + #[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(mut, signer)] buyer: AccountInfo<'info>, - #[account(mut, owner=spl_token::id(), token::mint=token_mint, token::authority=buyer)] + #[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>, - #[account(seeds=[PREFIX.as_bytes(), fair_launch.authority.as_ref(), MINT.as_bytes(), fair_launch.data.uuid.as_bytes()])] + #[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>, @@ -211,6 +245,7 @@ pub struct MedianTuple(pub u64, pub u64); pub struct FairLaunch { pub token_mint: Pubkey, pub treasury: Pubkey, + pub treasury_mint: Option, pub authority: Pubkey, pub bump: u8, pub treasury_bump: u8, @@ -268,4 +303,24 @@ pub enum ErrorCode { TokenTransferFailed, #[msg("Numerical overflow error")] NumericalOverflowError, + #[msg("Timestamps of phases should line up")] + TimestampsDontLineUp, + #[msg("Cant set phase 3 dates yet")] + CantSetPhaseThreeDatesYet, + #[msg("Uuid must be exactly of 6 length")] + UuidMustBeExactly6Length, + #[msg("Tick size too small")] + TickSizeTooSmall, + #[msg("Cannot give zero tokens")] + CannotGiveZeroTokens, + #[msg("Invalid price ranges")] + InvalidPriceRanges, + #[msg("With this tick size and price range, you will have too many ticks(>" + MAX_GRANULARITY + ") - choose less granularity")] + TooMuchGranularityInRange, + #[msg("Cannot use a tick size with a price range that results in a remainder when doing (end-start)/ticksize")] + CannotUseTickSizeThatGivesRemainder, + #[msg("Derived key invalid")] + DerivedKeyInvalid, + #[msg("Treasury Already Exists")] + TreasuryAlreadyExists } diff --git a/rust/fair-launch/src/utils.rs b/rust/fair-launch/src/utils.rs index 1473326..0dcb589 100644 --- a/rust/fair-launch/src/utils.rs +++ b/rust/fair-launch/src/utils.rs @@ -1,12 +1,15 @@ + use { - crate::ErrorCode, + crate::{ErrorCode,FairLaunchData,MAX_GRANULARITY}, anchor_lang::{ - prelude::{AccountInfo, ProgramError, ProgramResult, Pubkey}, + prelude::{AccountInfo, ProgramError, ProgramResult, Pubkey, Rent, msg, SolanaSysvar}, solana_program::{ - program::invoke_signed, + program::{invoke_signed, invoke}, + system_instruction, program_pack::{IsInitialized, Pack}, }, }, + std::convert::TryInto }; pub fn assert_initialized( @@ -69,3 +72,110 @@ pub fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult result.map_err(|_| ErrorCode::TokenTransferFailed.into()) } + +pub fn assert_data_valid(data: &FairLaunchData) -> ProgramResult { + if data.phase_one_end < data.phase_one_start { + 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()) + } + + if data.uuid.len() != 6 { + return Err(ErrorCode::UuidMustBeExactly6Length.into()) + } + + if data.tick_size == 0 { + return Err(ErrorCode::TickSizeTooSmall.into()) + } + + if data.number_of_tokens == 0 { + return Err(ErrorCode::CannotGiveZeroTokens.into()) + } + + if data.price_range_end < data.price_range_start || data.price_range_end == data.price_range_start { + 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)?; + + if remainder > 0 { + return Err(ErrorCode::CannotUseTickSizeThatGivesRemainder.into()) + } + + if possible_valid_user_prices > MAX_GRANULARITY { + return Err(ErrorCode::TooMuchGranularityInRange.into()) + } + + Ok(()) +} + +pub fn assert_derivation( + program_id: &Pubkey, + account: &AccountInfo, + path: &[&[u8]], +) -> Result { + let (key, bump) = Pubkey::find_program_address(&path, program_id); + if key != *account.key { + return Err(ErrorCode::DerivedKeyInvalid.into()); + } + 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)] +pub fn create_or_allocate_account_raw<'a>( + program_id: Pubkey, + new_account_info: &AccountInfo<'a>, + rent_sysvar_info: &AccountInfo<'a>, + system_program_info: &AccountInfo<'a>, + payer_info: &AccountInfo<'a>, + size: usize, + signer_seeds: &[&[u8]], +) -> Result<(), ProgramError> { + let rent = &Rent::from_account_info(rent_sysvar_info)?; + let required_lamports = rent + .minimum_balance(size) + .max(1) + .saturating_sub(new_account_info.lamports()); + + if required_lamports > 0 { + msg!("Transfer {} lamports to the new account", required_lamports); + invoke( + &system_instruction::transfer(&payer_info.key, new_account_info.key, required_lamports), + &[ + payer_info.clone(), + new_account_info.clone(), + system_program_info.clone(), + ], + )?; + } + + let accounts = &[new_account_info.clone(), system_program_info.clone()]; + + msg!("Allocate space for the account"); + invoke_signed( + &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()), + accounts, + &[&signer_seeds], + )?; + + msg!("Assign the account to the owning program"); + invoke_signed( + &system_instruction::assign(new_account_info.key, &program_id), + accounts, + &[&signer_seeds], + )?; + msg!("Completed assignation!"); + + Ok(()) +}