Skip to content

Instantly share code, notes, and snippets.

@texora
Created January 20, 2025 00:58
Show Gist options
  • Save texora/598a306ab55bcee920cc89a0c2bbab40 to your computer and use it in GitHub Desktop.
Save texora/598a306ab55bcee920cc89a0c2bbab40 to your computer and use it in GitHub Desktop.
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer};
use std::mem::size_of;
pub mod error;
use error::*;
pub mod constants;
use constants::*;
declare_id!("9ZmxLBeqLtxzJ2TRg5YdniyCcVuauB3aL3oTVBBK4Z4K");
#[program]
pub mod enrex_stake {
use super::*;
pub fn create_state(
_ctx: Context<CreateState>
) -> Result<()> {
let state = &mut _ctx.accounts.state;
state.authority = _ctx.accounts.authority.key();
state.start_time = _ctx.accounts.clock.unix_timestamp;
state.token_mint = _ctx.accounts.token_mint.key();
Ok(())
}
pub fn create_pool(
_ctx: Context<CreateFarmPool>,
pool_index: u8,
apy: u8,
min_stake_amount: u64,
lock_duration:i64
) -> Result<()> {
let pool = &mut _ctx.accounts.pool;
pool.vault = _ctx.accounts.vault.key();
pool.authority = _ctx.accounts.authority.key();
pool.min_stake_amount = min_stake_amount;
pool.lock_duration = lock_duration;
pool.apy = apy;
pool.index = pool_index;
Ok(())
}
pub fn fund_pool(_ctx: Context<FundPool>, amount: u64) -> Result<()> {
let pool = &mut _ctx.accounts.pool;
pool.amount_reward = pool.amount_reward.checked_add(amount).unwrap();
let cpi_accounts = Transfer {
from: _ctx.accounts.user_vault.to_account_info(),
to: _ctx.accounts.pool_vault.to_account_info(),
authority: _ctx.accounts.authority.to_account_info(),
};
let cpi_program = _ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
pub fn withdraw_pool(_ctx: Context<FundPool>, amount: u64) -> Result<()> {
let state = &_ctx.accounts.state;
let pool = &mut _ctx.accounts.pool;
require!(
pool.amount_reward - pool.amount_reward_reserved >= amount,
ErrorMsg::AlreadyReserved
);
pool.amount_reward -= amount;
let cpi_accounts = Transfer {
from: _ctx.accounts.pool_vault.to_account_info(),
to: _ctx.accounts.user_vault.to_account_info(),
authority: pool.to_account_info(),
};
let bump = bump(&[state.token_mint.as_ref(), &[pool.index]]);
let seeds = &[state.token_mint.as_ref(), &[pool.index], &[bump]];
let signer = &[&seeds[..]];
let cpi_program = _ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
pub fn stake(_ctx: Context<Stake>, amount: u64) -> Result<()> {
let pool = &mut _ctx.accounts.pool;
require!(amount >= pool.min_stake_amount,
ErrorMsg::BelowMinStakeAmount
);
let reward_amount = pool.get_reward_amount(amount) as u64;
let reward_amount_reserved = pool.amount_reward_reserved.checked_add(reward_amount).unwrap();
require!(reward_amount_reserved <= pool.amount_reward,
ErrorMsg::OverflowReservedReward
);
let staked_info = &mut _ctx.accounts.staked_info;
staked_info.amount = amount;
staked_info.staked_time = _ctx.accounts.clock.unix_timestamp;
staked_info.pool = pool.key();
staked_info.authority = _ctx.accounts.authority.key();
staked_info.stake_index = pool.inc_stakes;
staked_info.reward_amount = reward_amount;
pool.inc_stakes = pool.inc_stakes.checked_add(1).unwrap();
pool.count_stakes = pool.count_stakes.checked_add(1).unwrap();
pool.amount_reward_reserved = reward_amount_reserved;
pool.amount_staked = pool.amount_staked.checked_add(amount).unwrap();
let cpi_accounts = Transfer {
from: _ctx.accounts.user_vault.to_account_info(),
to: _ctx.accounts.pool_vault.to_account_info(),
authority: _ctx.accounts.authority.to_account_info(),
};
let cpi_program = _ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
pub fn cancel_stake(_ctx: Context<Unstake>) -> Result<()> {
let pool = &mut _ctx.accounts.pool;
let state = & _ctx.accounts.state;
let staked_info = &_ctx.accounts.staked_info;
let amount = staked_info.amount;
let cpi_accounts = Transfer {
from: _ctx.accounts.pool_vault.to_account_info(),
to: _ctx.accounts.user_vault.to_account_info(),
authority: pool.to_account_info(),
};
let bump = bump(&[state.token_mint.as_ref(), &[pool.index]]);
let seeds = &[state.token_mint.as_ref(), &[pool.index], &[bump]];
let signer = &[&seeds[..]];
let cpi_program = _ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
token::transfer(cpi_ctx, amount)?;
pool.count_stakes -= 1;
pool.amount_staked -= amount;
pool.amount_reward_reserved -= staked_info.reward_amount;
Ok(())
}
pub fn claim_stake(_ctx: Context<Unstake>) -> Result<()> {
let state = &_ctx.accounts.state;
let pool = &mut _ctx.accounts.pool;
let staked_info = &_ctx.accounts.staked_info;
require!(
staked_info.staked_time
.checked_add(pool.lock_duration)
.unwrap()
<= _ctx.accounts.clock.unix_timestamp,
ErrorMsg::UnderLocked
);
let amount = staked_info.amount.checked_add(staked_info.reward_amount).unwrap();
let cpi_accounts = Transfer {
from: _ctx.accounts.pool_vault.to_account_info(),
to: _ctx.accounts.user_vault.to_account_info(),
authority: pool.to_account_info(),
};
let bump = bump(&[state.token_mint.as_ref(), &[pool.index]]);
let seeds = &[state.token_mint.as_ref(), &[pool.index], &[bump]];
let signer = &[&seeds[..]];
let cpi_program = _ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
token::transfer(cpi_ctx, amount)?;
pool.count_stakes = pool.count_stakes.checked_sub(1).unwrap();
pool.amount_reward = pool.amount_reward.checked_sub(staked_info.reward_amount).unwrap();
pool.amount_reward_reserved = pool.amount_reward_reserved.checked_sub( staked_info.reward_amount).unwrap();
pool.amount_staked = pool.amount_staked.checked_sub( staked_info.amount).unwrap();
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateState<'info> {
#[account(
init,
seeds = [b"state".as_ref()],
bump,
payer = authority,
space = 8 + size_of::<StateAccount>()
)]
pub state: Account<'info, StateAccount>,
///pool's token mint
pub token_mint: Account<'info, Mint>,
#[account(mut)]
pub authority: Signer<'info>,
#[account(
constraint = system_program.key() == System::id()
)]
pub system_program: Program<'info, System>,
pub clock: Sysvar<'info, Clock>,
}
#[account()]
pub struct StateAccount {
///state creator
pub authority: Pubkey,
///token mint will be same through all pools
pub token_mint: Pubkey,
///just as additional info
pub start_time: i64,
}
#[derive(Accounts)]
#[instruction(pool_index: u8)]
pub struct CreateFarmPool<'info> {
#[account(
init,
seeds = [state.token_mint.as_ref(), &[pool_index]],
bump,
payer = authority,
space = 8 + size_of::<FarmPoolAccount>()
)]
pub pool: Account<'info, FarmPoolAccount>,
///only state creator can create a pool
#[account(mut, seeds = [b"state".as_ref()], bump, has_one = authority)]
pub state: Account<'info, StateAccount>,
///token account to receive and transfer stakes and rewards
#[account(
init,
token::mint = mint,
token::authority = pool,
seeds = [mint.key().as_ref(), pool.key().as_ref()],
bump,
payer = authority
)]
pub vault: Account<'info, TokenAccount>,
///validating if it is a correct mint that are initiated by the state creator
#[account(constraint = state.token_mint == mint.key())]
pub mint: Account<'info, Mint>,
///pool creator
#[account(mut)]
pub authority: Signer<'info>,
#[account(
constraint = system_program.key() == System::id()
)]
pub system_program: Program<'info, System>,
#[account(constraint = token_program.key == &token::ID)]
pub token_program: Program<'info, Token>,
pub clock: Sysvar<'info, Clock>,
pub rent: Sysvar<'info, Rent>,
}
#[account()]
pub struct FarmPoolAccount {
///farm creator
pub authority: Pubkey,
pub amount_staked: u64,
pub amount_reward: u64,
pub amount_reward_reserved: u64,
pub min_stake_amount: u64,
pub vault: Pubkey,
///count of active stakes
pub count_stakes: u64,
//only increase - it is used to derive unique stake info account(A user creates a new stake info account whenever he stakes)
pub inc_stakes: u64,
pub lock_duration: i64,
///apy as percentage value
pub apy: u8,
///it is used as a pool_index when deriving a pool's pda
pub index: u8
}
impl FarmPoolAccount {
///calculate reward amount from the stake amount
pub fn get_reward_amount(&self, amount: u64) -> u128 {
let apy = self.apy;
// let lock_duration_in_days = u128::from(ACC_PRECISION)
// .checked_mul(self.lock_duration as u128)
// .unwrap()
// .checked_div(3600 * 24)
// .unwrap();
// let percentage = u128::from(lock_duration_in_days)
// .checked_mul(u128::from(apy))
// .unwrap()
// .checked_div(36525)// 365.25 (days) * 100 (%)
// .unwrap();
u128::from(amount)
.checked_mul(u128::from(apy))
.unwrap()
.checked_mul(self.lock_duration as u128)
.unwrap()
.checked_mul(u128::from(ACC_PRECISION))
.unwrap()
.checked_div(36525 * 3600 * 24)// 365.25 (days) * 100 (%)
.unwrap()
.checked_div(ACC_PRECISION)
.unwrap()
}
}
#[derive(Accounts)]
pub struct FundPool<'info> {
///only admin can fund the pool
#[account(mut,
seeds = [b"state".as_ref()],
bump,
has_one = authority)
]
pub state: Account<'info, StateAccount>,
#[account(mut,
seeds = [state.token_mint.as_ref(), &[pool.index]],
bump,
)]
pub pool: Account<'info, FarmPoolAccount>,
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut,
seeds = [
state.token_mint.as_ref(),
pool.key().as_ref()],
bump
)]
pub pool_vault: Account<'info, TokenAccount>,
///funder's token account should be admin's
#[account(mut, constraint = user_vault.owner == authority.key())]
pub user_vault: Account<'info, TokenAccount>,
#[account(constraint = token_program.key == &token::ID)]
pub token_program: Program<'info, Token>
}
#[account()]
pub struct StakedInfo {
///staked pool
pub pool: Pubkey,
///staker
pub authority: Pubkey,
///stake amount
pub amount: u64,
///reward amount
pub reward_amount: u64,
pub staked_time: i64,
///index of the StakedInfo account. Pool account increases inc_stakes whenever there is a stake, and use it for deriving StakedInfo pda
pub stake_index: u64
}
#[derive(Accounts)]
pub struct Stake<'info> {
#[account(
init,
seeds= [
b"stake-info".as_ref(),
pool.key().as_ref(),
authority.key().as_ref(),
pool.inc_stakes.to_be_bytes().as_ref()],
bump,
space = 8 + size_of::<StakedInfo>(),
payer = authority
)]
pub staked_info: Account<'info, StakedInfo>,
#[account(mut, seeds = [b"state".as_ref()], bump)]
pub state: Account<'info, StateAccount>,
#[account(mut,
seeds = [state.token_mint.as_ref(), &[pool.index]],
bump)]
pub pool: Account<'info, FarmPoolAccount>,
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut,
seeds = [state.token_mint.as_ref(), pool.key().as_ref()],
bump
)]
pub pool_vault: Account<'info, TokenAccount>,
#[account(mut, constraint = user_vault.owner == authority.key())]
pub user_vault: Account<'info, TokenAccount>,
#[account(constraint = token_program.key == &token::ID)]
pub token_program: Program<'info, Token>,
pub clock: Sysvar<'info, Clock>,
#[account(
constraint = system_program.key() == System::id()
)]
pub system_program: Program<'info, System>,
}
///This context is used in unstaking and cancelling
#[derive(Accounts)]
pub struct Unstake<'info> {
#[account(mut,
seeds= [
b"stake-info".as_ref(),
staked_info.pool.as_ref(),
staked_info.authority.as_ref(),
staked_info.stake_index.to_be_bytes().as_ref()],
bump,
has_one = authority,
has_one = pool,
close = authority
)]
pub staked_info: Account<'info, StakedInfo>,
#[account(mut, seeds = [b"state".as_ref()], bump)]
pub state: Account<'info, StateAccount>,
#[account(mut,
seeds = [state.token_mint.as_ref(), &[pool.index]],
bump)]
pub pool: Account<'info, FarmPoolAccount>,
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut,
seeds = [state.token_mint.as_ref(), pool.key().as_ref()],
bump
)]
pub pool_vault: Account<'info, TokenAccount>,
#[account(mut, constraint = user_vault.owner == authority.key())]
pub user_vault: Account<'info, TokenAccount>,
#[account(constraint = token_program.key == &token::ID)]
pub token_program: Program<'info, Token>,
pub clock: Sysvar<'info, Clock>,
#[account(
constraint = system_program.key() == System::id()
)]
pub system_program: Program<'info, System>,
}
pub fn bump(seeds: &[&[u8]]) -> u8 {
let program_id = crate::ID;
let (_found_key, bump) = Pubkey::find_program_address(seeds, &program_id);
bump
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment