Anti rug commit - before testing.
This commit is contained in:
parent
d8e096e3af
commit
34ad88f514
|
@ -726,6 +726,7 @@ program
|
|||
const exists = await anchorProgram.provider.connection.getAccountInfo(
|
||||
fairLaunchLotteryBitmap,
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
await anchorProgram.rpc.createFairLaunchLotteryBitmap(bump, {
|
||||
accounts: {
|
||||
|
|
|
@ -3,8 +3,9 @@ pub mod utils;
|
|||
use {
|
||||
crate::utils::{
|
||||
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,
|
||||
assert_valid_amount, calculate_refund_amount, calculate_withdraw_amount,
|
||||
create_or_allocate_account_raw, get_mask_and_index_for_seq, spl_token_burn,
|
||||
spl_token_mint_to, spl_token_transfer, TokenBurnParams, TokenTransferParams,
|
||||
},
|
||||
anchor_lang::{
|
||||
prelude::*,
|
||||
|
@ -660,6 +661,7 @@ pub mod fair_launch {
|
|||
let fair_launch = &mut ctx.accounts.fair_launch;
|
||||
let treasury = &mut ctx.accounts.treasury;
|
||||
let authority = &mut ctx.accounts.authority;
|
||||
let token_mint = &ctx.accounts.token_mint;
|
||||
|
||||
if fair_launch.number_tickets_sold
|
||||
> fair_launch.number_tickets_dropped + fair_launch.number_tickets_punched
|
||||
|
@ -671,9 +673,12 @@ pub mod fair_launch {
|
|||
return Err(ErrorCode::CannotCashOutUntilPhaseThree.into());
|
||||
}
|
||||
|
||||
let mint: spl_token::state::Mint = assert_initialized(token_mint)?;
|
||||
let tokens = mint.supply;
|
||||
|
||||
let signer_seeds = [
|
||||
PREFIX.as_bytes(),
|
||||
fair_launch.token_mint.as_ref(),
|
||||
&token_mint.key.as_ref(),
|
||||
&[fair_launch.bump],
|
||||
];
|
||||
|
||||
|
@ -719,17 +724,39 @@ pub mod fair_launch {
|
|||
return Err(ErrorCode::AccountShouldHaveNoDelegates.into());
|
||||
}
|
||||
|
||||
if fair_launch.treasury_snapshot.is_none() {
|
||||
fair_launch.treasury_snapshot = Some(treasury_account.amount)
|
||||
}
|
||||
|
||||
let amount = calculate_withdraw_amount(
|
||||
&fair_launch.data,
|
||||
tokens,
|
||||
fair_launch.treasury_snapshot.unwrap(),
|
||||
treasury_account.amount,
|
||||
)?;
|
||||
|
||||
spl_token_transfer(TokenTransferParams {
|
||||
source: treasury.to_account_info(),
|
||||
destination: authority_token_account_info.clone(),
|
||||
authority: fair_launch.to_account_info(),
|
||||
authority_signer_seeds: &signer_seeds,
|
||||
token_program: token_program.clone(),
|
||||
amount: treasury_account.amount,
|
||||
amount,
|
||||
})?;
|
||||
} else {
|
||||
if fair_launch.treasury_snapshot.is_none() {
|
||||
fair_launch.treasury_snapshot = Some(treasury.lamports())
|
||||
}
|
||||
|
||||
let amount = calculate_withdraw_amount(
|
||||
&fair_launch.data,
|
||||
tokens,
|
||||
fair_launch.treasury_snapshot.unwrap(),
|
||||
treasury.lamports(),
|
||||
)?;
|
||||
|
||||
invoke(
|
||||
&system_instruction::transfer(treasury.key, authority.key, treasury.lamports()),
|
||||
&system_instruction::transfer(treasury.key, authority.key, amount),
|
||||
&[
|
||||
treasury.to_account_info(),
|
||||
authority.clone(),
|
||||
|
@ -742,6 +769,117 @@ pub mod fair_launch {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn receive_refund<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, ReceiveRefund<'info>>,
|
||||
) -> ProgramResult {
|
||||
let fair_launch = &mut ctx.accounts.fair_launch;
|
||||
let treasury = &mut ctx.accounts.treasury;
|
||||
let buyer = &mut ctx.accounts.buyer;
|
||||
let token_mint = &ctx.accounts.token_mint;
|
||||
let token_program = &ctx.accounts.token_program;
|
||||
let clock = &ctx.accounts.clock;
|
||||
let buyer_token_account = &mut ctx.accounts.buyer_token_account;
|
||||
let transfer_authority = &mut ctx.accounts.transfer_authority;
|
||||
|
||||
let signer_seeds = [
|
||||
PREFIX.as_bytes(),
|
||||
&token_mint.key.as_ref(),
|
||||
&[fair_launch.bump],
|
||||
];
|
||||
|
||||
if fair_launch.number_tickets_sold
|
||||
> fair_launch.number_tickets_dropped + fair_launch.number_tickets_punched
|
||||
{
|
||||
return Err(ErrorCode::CannotRefundUntilAllTicketsHaveBeenPunchedOrDropped.into());
|
||||
}
|
||||
|
||||
if !fair_launch.phase_three_started {
|
||||
return Err(ErrorCode::CannotRefundUntilPhaseThree.into());
|
||||
}
|
||||
|
||||
fair_launch.number_tokens_burned_for_refunds = fair_launch
|
||||
.number_tokens_burned_for_refunds
|
||||
.checked_add(1)
|
||||
.ok_or(ErrorCode::NumericalOverflowError)?;
|
||||
|
||||
spl_token_burn(TokenBurnParams {
|
||||
mint: token_mint.clone(),
|
||||
source: buyer_token_account.clone(),
|
||||
amount: 1,
|
||||
authority: transfer_authority.clone(),
|
||||
authority_signer_seeds: None,
|
||||
token_program: token_program.clone(),
|
||||
})?;
|
||||
|
||||
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_payment_account_info = &ctx.remaining_accounts[1];
|
||||
let buyer_payment_account: Account = assert_initialized(&buyer_payment_account_info)?;
|
||||
let treasury_account: Account = assert_initialized(treasury)?;
|
||||
|
||||
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_payment_account_info, &token_program.key)?;
|
||||
assert_owned_by(treasury, &token_program.key)?;
|
||||
|
||||
if buyer_payment_account.mint != *treasury_mint_info.key {
|
||||
return Err(ErrorCode::TreasuryMintMismatch.into());
|
||||
}
|
||||
|
||||
// assert is an ATA
|
||||
assert_derivation(
|
||||
&Pubkey::from_str(ASSOCIATED_TOKEN_PROGRAM_ID).unwrap(),
|
||||
buyer_payment_account_info,
|
||||
&[
|
||||
buyer.key.as_ref(),
|
||||
token_program.key.as_ref(),
|
||||
&treasury_mint_info.key.as_ref(),
|
||||
],
|
||||
)?;
|
||||
|
||||
if buyer_payment_account.delegate.is_some() {
|
||||
return Err(ErrorCode::AccountShouldHaveNoDelegates.into());
|
||||
}
|
||||
|
||||
if fair_launch.treasury_snapshot.is_none() {
|
||||
fair_launch.treasury_snapshot = Some(treasury_account.amount)
|
||||
}
|
||||
|
||||
let amount = calculate_refund_amount(fair_launch, clock.unix_timestamp)?;
|
||||
|
||||
spl_token_transfer(TokenTransferParams {
|
||||
source: treasury.to_account_info(),
|
||||
destination: buyer_payment_account_info.clone(),
|
||||
authority: fair_launch.to_account_info(),
|
||||
authority_signer_seeds: &signer_seeds,
|
||||
token_program: token_program.clone(),
|
||||
amount,
|
||||
})?;
|
||||
} else {
|
||||
if fair_launch.treasury_snapshot.is_none() {
|
||||
fair_launch.treasury_snapshot = Some(treasury.lamports())
|
||||
}
|
||||
|
||||
let amount = calculate_refund_amount(fair_launch, clock.unix_timestamp)?;
|
||||
|
||||
invoke(
|
||||
&system_instruction::transfer(treasury.key, buyer.key, amount),
|
||||
&[
|
||||
treasury.to_account_info(),
|
||||
buyer.clone(),
|
||||
ctx.accounts.system_program.clone(),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(bump: u8, treasury_bump: u8, token_mint_bump: u8, data: FairLaunchData)]
|
||||
pub struct InitializeFairLaunch<'info> {
|
||||
|
@ -901,12 +1039,14 @@ pub struct PunchTicket<'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)]
|
||||
#[account(mut, 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)]
|
||||
authority: AccountInfo<'info>,
|
||||
#[account(mut, 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 = system_program::ID)]
|
||||
system_program: AccountInfo<'info>,
|
||||
// Remaining accounts in this order if using spl tokens for payment:
|
||||
|
@ -915,6 +1055,30 @@ pub struct WithdrawFunds<'info> {
|
|||
// [optional] token program
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ReceiveRefund<'info> {
|
||||
#[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>,
|
||||
#[account(mut)]
|
||||
buyer: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
buyer_token_account: AccountInfo<'info>,
|
||||
#[account(signer)]
|
||||
transfer_authority: AccountInfo<'info>,
|
||||
#[account(mut, 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>,
|
||||
#[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 payment token account (must be ata)
|
||||
}
|
||||
|
||||
pub const FAIR_LAUNCH_LOTTERY_SIZE: usize = 8 + // discriminator
|
||||
32 + // fair launch
|
||||
1 + // bump
|
||||
|
@ -944,7 +1108,10 @@ pub const FAIR_LAUNCH_SPACE_VEC_START: usize = 8 + // discriminator
|
|||
8 + // number of tickets sold
|
||||
8 + // number of tickets dropped
|
||||
8 + // number of tickets punched
|
||||
8 + // number of tokens burned for refunds
|
||||
8 + // number of tokens preminted
|
||||
1 + // phase three started
|
||||
9 + // treasury snapshot
|
||||
8 + // current median,
|
||||
4 + // u32 representing number of amounts in vec so far
|
||||
100; // padding
|
||||
|
@ -1000,11 +1167,22 @@ pub struct FairLaunch {
|
|||
pub treasury_bump: u8,
|
||||
pub token_mint_bump: u8,
|
||||
pub data: FairLaunchData,
|
||||
/// Tickets that are missing a corresponding seq pda. Crank it.
|
||||
pub number_tickets_un_seqed: u64,
|
||||
/// If I have to explain this, you're an idiot.
|
||||
pub number_tickets_sold: u64,
|
||||
/// People that withdrew in phase 2 because they dislike you.
|
||||
pub number_tickets_dropped: u64,
|
||||
/// People who won the lottery and punched ticket in exchange for token. Good job!
|
||||
pub number_tickets_punched: u64,
|
||||
/// if you go past refund date, here is how many people lost faith in you.
|
||||
pub number_tokens_burned_for_refunds: u64,
|
||||
/// here is how many tokens you preminted before people had access. SHAME. *bell*
|
||||
pub number_tokens_preminted: u64,
|
||||
/// Yes.
|
||||
pub phase_three_started: bool,
|
||||
/// Snapshot of treasury taken on first withdrawal.
|
||||
pub treasury_snapshot: Option<u64>,
|
||||
pub current_median: u64,
|
||||
pub counts_at_each_tick: Vec<u64>,
|
||||
}
|
||||
|
@ -1121,4 +1299,22 @@ pub enum ErrorCode {
|
|||
PhaseTwoEnded,
|
||||
#[msg("Cannot punch ticket when having paid less than median.")]
|
||||
CannotPunchTicketWhenHavingPaidLessThanMedian,
|
||||
#[msg("You have already withdrawn your seed capital alotment from the treasury.")]
|
||||
AlreadyWithdrawnCapitalAlotment,
|
||||
#[msg("No anti rug settings on this fair launch. Should've checked twice.")]
|
||||
NoAntiRugSetting,
|
||||
#[msg("Self destruct date has not passed yet, so you are not eligible for a refund.")]
|
||||
SelfDestructNotPassed,
|
||||
#[msg("Token burn failed")]
|
||||
TokenBurnFailed,
|
||||
#[msg("No treasury snapshot present")]
|
||||
NoTreasurySnapshot,
|
||||
#[msg("Cannot refund until all existing tickets have been dropped or punched")]
|
||||
CannotRefundUntilAllTicketsHaveBeenPunchedOrDropped,
|
||||
#[msg("Cannot refund until phase three")]
|
||||
CannotRefundUntilPhaseThree,
|
||||
#[msg("Invalid reserve bp")]
|
||||
InvalidReserveBp,
|
||||
#[msg("Anti Rug Token Requirement must be less than or equal to number of tokens being sold")]
|
||||
InvalidAntiRugTokenRequirement,
|
||||
}
|
||||
|
|
|
@ -207,6 +207,16 @@ pub fn assert_data_valid(data: &FairLaunchData) -> ProgramResult {
|
|||
return Err(ErrorCode::InvalidPriceRanges.into());
|
||||
}
|
||||
|
||||
if let Some(anti_rug) = &data.anti_rug_setting {
|
||||
if anti_rug.reserve_bp > 10000 {
|
||||
return Err(ErrorCode::InvalidReserveBp.into());
|
||||
}
|
||||
|
||||
if anti_rug.token_requirement > data.number_of_tokens {
|
||||
return Err(ErrorCode::InvalidAntiRugTokenRequirement.into());
|
||||
}
|
||||
}
|
||||
|
||||
let difference = data
|
||||
.price_range_end
|
||||
.checked_sub(data.price_range_start)
|
||||
|
@ -229,6 +239,86 @@ pub fn assert_data_valid(data: &FairLaunchData) -> ProgramResult {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn calculate_refund_amount(
|
||||
fair_launch: &ProgramAccount<FairLaunch>,
|
||||
unix_timestamp: i64,
|
||||
) -> Result<u64, ProgramError> {
|
||||
if let Some(anti_rug) = &fair_launch.data.anti_rug_setting {
|
||||
if unix_timestamp < anti_rug.self_destruct_date {
|
||||
return Err(ErrorCode::SelfDestructNotPassed.into());
|
||||
}
|
||||
if let Some(snapshot) = fair_launch.treasury_snapshot {
|
||||
let reserve_size = snapshot
|
||||
.checked_sub(get_expected_capital_alotment_size(
|
||||
anti_rug.reserve_bp,
|
||||
snapshot,
|
||||
)?)
|
||||
.ok_or(ErrorCode::NumericalOverflowError)?;
|
||||
|
||||
msg!(
|
||||
"calculated reserve size total is {} dividing by number tickets punched {}",
|
||||
reserve_size,
|
||||
fair_launch.number_tickets_punched
|
||||
);
|
||||
|
||||
let my_slice = (reserve_size)
|
||||
.checked_div(fair_launch.number_tickets_punched)
|
||||
.ok_or(ErrorCode::NumericalOverflowError)?;
|
||||
|
||||
msg!("My slice is {}", my_slice);
|
||||
|
||||
Ok(my_slice)
|
||||
} else {
|
||||
return Err(ErrorCode::NoTreasurySnapshot.into());
|
||||
}
|
||||
} else {
|
||||
return Err(ErrorCode::NoAntiRugSetting.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_withdraw_amount(
|
||||
data: &FairLaunchData,
|
||||
supply: u64,
|
||||
snapshot: u64,
|
||||
real_amount: u64,
|
||||
) -> Result<u64, ProgramError> {
|
||||
let amount_to_withdraw = if let Some(anti_rug) = &data.anti_rug_setting {
|
||||
if supply <= anti_rug.token_requirement {
|
||||
msg!("Deal satisfied. You can withdraw it all!");
|
||||
real_amount
|
||||
} else {
|
||||
if snapshot != real_amount {
|
||||
return Err(ErrorCode::AlreadyWithdrawnCapitalAlotment.into());
|
||||
}
|
||||
get_expected_capital_alotment_size(anti_rug.reserve_bp, snapshot)?
|
||||
}
|
||||
} else {
|
||||
real_amount
|
||||
};
|
||||
|
||||
Ok(amount_to_withdraw)
|
||||
}
|
||||
|
||||
pub fn get_expected_capital_alotment_size(
|
||||
reserve_bp: u16,
|
||||
snapshot: u64,
|
||||
) -> Result<u64, ProgramError> {
|
||||
let non_reserve_frac: u128 = 10000u128 - reserve_bp as u128;
|
||||
msg!("Non reserve frac {}", non_reserve_frac);
|
||||
let numerator: u128 = (snapshot as u128)
|
||||
.checked_mul(non_reserve_frac)
|
||||
.ok_or(ErrorCode::NumericalOverflowError)?;
|
||||
msg!("Numerator {}", numerator);
|
||||
let divided = numerator
|
||||
.checked_div(10000)
|
||||
.ok_or(ErrorCode::NumericalOverflowError)?;
|
||||
msg!(
|
||||
"Numerator divided by 10000 {} is amount to withdrawal",
|
||||
divided
|
||||
);
|
||||
Ok(divided as u64)
|
||||
}
|
||||
|
||||
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());
|
||||
|
@ -327,3 +417,47 @@ pub fn spl_token_mint_to<'a: 'b, 'b>(
|
|||
);
|
||||
result.map_err(|_| ErrorCode::TokenMintToFailed.into())
|
||||
}
|
||||
|
||||
/// TokenBurnParams
|
||||
pub struct TokenBurnParams<'a: 'b, 'b> {
|
||||
/// mint
|
||||
pub mint: AccountInfo<'a>,
|
||||
/// source
|
||||
pub source: AccountInfo<'a>,
|
||||
/// amount
|
||||
pub amount: u64,
|
||||
/// authority
|
||||
pub authority: AccountInfo<'a>,
|
||||
/// authority_signer_seeds
|
||||
pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
|
||||
/// token_program
|
||||
pub token_program: AccountInfo<'a>,
|
||||
}
|
||||
|
||||
pub fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
|
||||
let TokenBurnParams {
|
||||
mint,
|
||||
source,
|
||||
authority,
|
||||
token_program,
|
||||
amount,
|
||||
authority_signer_seeds,
|
||||
} = params;
|
||||
let mut seeds: Vec<&[&[u8]]> = vec![];
|
||||
if let Some(seed) = authority_signer_seeds {
|
||||
seeds.push(seed);
|
||||
}
|
||||
let result = invoke_signed(
|
||||
&spl_token::instruction::burn(
|
||||
token_program.key,
|
||||
source.key,
|
||||
mint.key,
|
||||
authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?,
|
||||
&[source, mint, authority, token_program],
|
||||
seeds.as_slice(),
|
||||
);
|
||||
result.map_err(|_| ErrorCode::TokenBurnFailed.into())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue