Working on fair launch
This commit is contained in:
parent
db1a6980ee
commit
6d323f8baf
|
@ -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<InitializeFairLaunch>) -> ProgramResult {
|
||||
pub fn initialize_fair_launch(ctx: Context<InitializeFairLaunch>, 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<Pubkey>,
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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<T: Pack + IsInitialized>(
|
||||
|
@ -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<u8, ProgramError> {
|
||||
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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue