#![deny(rustdoc::all)]
#![allow(rustdoc::missing_doc_code_examples)]
#![allow(deprecated)]
#[macro_use]
mod macros;
mod state;
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use anchor_spl::token::{self, Mint, TokenAccount, Transfer};
use payroll::Payroll;
pub use state::*;
use vipers::prelude::*;
pub mod account_validators;
pub mod addresses;
pub mod payroll;
pub mod quarry;
pub mod rewarder;
mod instructions;
pub use instructions::*;
use crate::quarry::StakeAction;
declare_id!("QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB");
pub const MAX_ANNUAL_REWARDS_RATE: u64 = u64::MAX >> 3;
pub const DEFAULT_CLAIM_FEE_MILLIBPS: u64 = 1_000;
pub const MAX_BPS: u64 = 10_000;
#[cfg(not(feature = "no-entrypoint"))]
solana_security_txt::security_txt! {
name: "Quarry Mine",
project_url: "https://quarry.so",
contacts: "email:team@quarry.so",
policy: "https://github.com/QuarryProtocol/quarry/blob/master/SECURITY.md",
source_code: "https://github.com/QuarryProtocol/quarry",
auditors: "Quantstamp"
}
#[program]
pub mod quarry_mine {
use super::*;
#[deprecated(since = "5.0.0", note = "Use `new_rewarder_v2` instead.")]
#[access_control(ctx.accounts.validate())]
pub fn new_rewarder(ctx: Context<NewRewarder>, _bump: u8) -> Result<()> {
instructions::new_rewarder::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn new_rewarder_v2(ctx: Context<NewRewarderV2>) -> Result<()> {
instructions::new_rewarder_v2::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn set_pause_authority(ctx: Context<SetPauseAuthority>) -> Result<()> {
let rewarder = &mut ctx.accounts.auth.rewarder;
rewarder.pause_authority = ctx.accounts.new_pause_authority.key();
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn pause(ctx: Context<MutableRewarderWithPauseAuthority>) -> Result<()> {
let rewarder = &mut ctx.accounts.rewarder;
rewarder.is_paused = true;
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn unpause(ctx: Context<MutableRewarderWithPauseAuthority>) -> Result<()> {
let rewarder = &mut ctx.accounts.rewarder;
rewarder.is_paused = false;
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn transfer_authority(
ctx: Context<TransferAuthority>,
new_authority: Pubkey,
) -> Result<()> {
let rewarder = &mut ctx.accounts.rewarder;
rewarder.pending_authority = new_authority;
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn accept_authority(ctx: Context<AcceptAuthority>) -> Result<()> {
let rewarder = &mut ctx.accounts.rewarder;
let next_authority = rewarder.pending_authority;
rewarder.authority = next_authority;
rewarder.pending_authority = Pubkey::default();
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn set_annual_rewards(ctx: Context<SetAnnualRewards>, new_rate: u64) -> Result<()> {
invariant!(
new_rate <= MAX_ANNUAL_REWARDS_RATE,
MaxAnnualRewardsRateExceeded
);
let rewarder = &mut ctx.accounts.auth.rewarder;
let previous_rate = rewarder.annual_rewards_rate;
rewarder.annual_rewards_rate = new_rate;
let current_ts = Clock::get()?.unix_timestamp;
emit!(RewarderAnnualRewardsUpdateEvent {
previous_rate,
new_rate,
timestamp: current_ts,
});
Ok(())
}
#[deprecated(since = "5.0.0", note = "Use `create_quarry_v2` instead.")]
#[access_control(ctx.accounts.validate())]
pub fn create_quarry(ctx: Context<CreateQuarry>, _bump: u8) -> Result<()> {
instructions::create_quarry::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn create_quarry_v2(ctx: Context<CreateQuarryV2>) -> Result<()> {
instructions::create_quarry_v2::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn set_rewards_share(ctx: Context<SetRewardsShare>, new_share: u64) -> Result<()> {
let rewarder = &mut ctx.accounts.auth.rewarder;
let quarry = &mut ctx.accounts.quarry;
rewarder.total_rewards_shares = unwrap_int!(rewarder
.total_rewards_shares
.checked_add(new_share)
.and_then(|v| v.checked_sub(quarry.rewards_share)));
quarry.rewards_share = new_share;
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn set_famine(ctx: Context<SetFamine>, famine_ts: i64) -> Result<()> {
let quarry = &mut ctx.accounts.quarry;
quarry.famine_ts = famine_ts;
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn update_quarry_rewards(ctx: Context<UpdateQuarryRewards>) -> Result<()> {
let current_ts = Clock::get()?.unix_timestamp;
let rewarder = &ctx.accounts.rewarder;
let payroll: Payroll = (*ctx.accounts.quarry).into();
let quarry = &mut ctx.accounts.quarry;
quarry.update_rewards_internal(current_ts, rewarder, &payroll)?;
emit!(QuarryRewardsUpdateEvent {
token_mint: quarry.token_mint_key,
annual_rewards_rate: quarry.annual_rewards_rate,
rewards_share: quarry.rewards_share,
timestamp: current_ts,
});
Ok(())
}
#[deprecated(since = "5.0.0", note = "Use `create_miner_v2` instead.")]
#[access_control(ctx.accounts.validate())]
pub fn create_miner(ctx: Context<CreateMiner>, _bump: u8) -> Result<()> {
instructions::create_miner::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn create_miner_v2(ctx: Context<CreateMiner>) -> Result<()> {
instructions::create_miner::handler(ctx)
}
#[deprecated(since = "5.0.0", note = "Use `claim_rewards_v2` instead.")]
#[access_control(ctx.accounts.validate())]
pub fn claim_rewards(ctx: Context<ClaimRewards>) -> Result<()> {
instructions::claim_rewards::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn claim_rewards_v2(ctx: Context<ClaimRewardsV2>) -> Result<()> {
instructions::claim_rewards_v2::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn stake_tokens(ctx: Context<UserStake>, amount: u64) -> Result<()> {
if amount == 0 {
return Ok(());
}
let quarry = &mut ctx.accounts.quarry;
let clock = Clock::get()?;
quarry.process_stake_action_internal(
StakeAction::Stake,
clock.unix_timestamp,
&ctx.accounts.rewarder,
&mut ctx.accounts.miner,
amount,
)?;
let cpi_accounts = Transfer {
from: ctx.accounts.token_account.to_account_info(),
to: ctx.accounts.miner_vault.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_context, amount)?;
emit!(StakeEvent {
timestamp: clock.unix_timestamp,
authority: ctx.accounts.authority.key(),
amount,
token: ctx.accounts.token_account.mint,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn withdraw_tokens(ctx: Context<UserStake>, amount: u64) -> Result<()> {
if amount == 0 {
return Ok(());
}
invariant!(
amount <= ctx.accounts.miner_vault.amount,
InsufficientBalance
);
let clock = Clock::get()?;
let quarry = &mut ctx.accounts.quarry;
quarry.process_stake_action_internal(
StakeAction::Withdraw,
clock.unix_timestamp,
&ctx.accounts.rewarder,
&mut ctx.accounts.miner,
amount,
)?;
let miner_seeds = &[
b"Miner".as_ref(),
ctx.accounts.miner.quarry.as_ref(),
ctx.accounts.miner.authority.as_ref(),
&[ctx.accounts.miner.bump],
];
let signer_seeds = &[&miner_seeds[..]];
let cpi_accounts = token::Transfer {
from: ctx.accounts.miner_vault.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.miner.to_account_info(),
};
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
token::transfer(cpi_ctx, amount)?;
emit!(WithdrawEvent {
timestamp: clock.unix_timestamp,
authority: ctx.accounts.authority.key(),
amount,
token: ctx.accounts.token_account.mint,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn rescue_tokens(ctx: Context<RescueTokens>) -> Result<()> {
instructions::rescue_tokens::handler(ctx)
}
#[access_control(ctx.accounts.validate())]
pub fn extract_fees(ctx: Context<ExtractFees>) -> Result<()> {
let seeds = gen_rewarder_signer_seeds!(ctx.accounts.rewarder);
let signer_seeds = &[&seeds[..]];
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.claim_fee_token_account.to_account_info(),
to: ctx.accounts.fee_to_token_account.to_account_info(),
authority: ctx.accounts.rewarder.to_account_info(),
},
signer_seeds,
),
ctx.accounts.claim_fee_token_account.amount,
)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct SetPauseAuthority<'info> {
pub auth: MutableRewarderWithAuthority<'info>,
pub new_pause_authority: UncheckedAccount<'info>,
}
#[derive(Accounts)]
pub struct TransferAuthority<'info> {
pub authority: Signer<'info>,
#[account(mut)]
pub rewarder: Account<'info, Rewarder>,
}
#[derive(Accounts)]
pub struct AcceptAuthority<'info> {
pub authority: Signer<'info>,
#[account(mut)]
pub rewarder: Account<'info, Rewarder>,
}
#[derive(Accounts, Clone)]
pub struct MutableRewarderWithAuthority<'info> {
pub authority: Signer<'info>,
#[account(mut, has_one = authority @ ErrorCode::Unauthorized)]
pub rewarder: Account<'info, Rewarder>,
}
#[derive(Accounts)]
pub struct ReadOnlyRewarderWithAuthority<'info> {
pub authority: Signer<'info>,
#[account(has_one = authority)]
pub rewarder: Account<'info, Rewarder>,
}
#[derive(Accounts)]
pub struct SetAnnualRewards<'info> {
pub auth: MutableRewarderWithAuthority<'info>,
}
#[derive(Accounts)]
pub struct SetFamine<'info> {
pub auth: ReadOnlyRewarderWithAuthority<'info>,
#[account(mut, constraint = quarry.rewarder == auth.rewarder.key())]
pub quarry: Account<'info, Quarry>,
}
#[derive(Accounts)]
pub struct SetRewardsShare<'info> {
pub auth: MutableRewarderWithAuthority<'info>,
#[account(mut)]
pub quarry: Account<'info, Quarry>,
}
#[derive(Accounts)]
pub struct UpdateQuarryRewards<'info> {
#[account(mut)]
pub quarry: Account<'info, Quarry>,
pub rewarder: Account<'info, Rewarder>,
}
#[derive(Accounts, Clone)]
pub struct UserStake<'info> {
pub authority: Signer<'info>,
#[account(mut)]
pub miner: Account<'info, Miner>,
#[account(mut)]
pub quarry: Account<'info, Quarry>,
#[account(mut)]
pub miner_vault: Account<'info, TokenAccount>,
#[account(mut)]
pub token_account: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
pub rewarder: Account<'info, Rewarder>,
}
#[derive(Accounts)]
pub struct ExtractFees<'info> {
#[account(has_one = claim_fee_token_account)]
pub rewarder: Account<'info, Rewarder>,
#[account(mut)]
pub claim_fee_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub fee_to_token_account: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct MutableRewarderWithPauseAuthority<'info> {
pub pause_authority: Signer<'info>,
#[account(mut)]
pub rewarder: Account<'info, Rewarder>,
}
#[event]
pub struct StakeEvent {
#[index]
pub authority: Pubkey,
#[index]
pub token: Pubkey,
pub amount: u64,
pub timestamp: i64,
}
#[event]
pub struct WithdrawEvent {
#[index]
pub authority: Pubkey,
#[index]
pub token: Pubkey,
pub amount: u64,
pub timestamp: i64,
}
#[event]
pub struct RewarderAnnualRewardsUpdateEvent {
pub previous_rate: u64,
pub new_rate: u64,
pub timestamp: i64,
}
#[event]
pub struct QuarryRewardsUpdateEvent {
pub token_mint: Pubkey,
pub annual_rewards_rate: u64,
pub rewards_share: u64,
pub timestamp: i64,
}
#[error_code]
pub enum ErrorCode {
#[msg("You are not authorized to perform this action.")]
Unauthorized,
#[msg("Insufficient staked balance for withdraw request.")]
InsufficientBalance,
#[msg("Pending authority not set")]
PendingAuthorityNotSet,
#[msg("Invalid quarry rewards share")]
InvalidRewardsShare,
#[msg("Insufficient allowance.")]
InsufficientAllowance,
#[msg("New vault not empty.")]
NewVaultNotEmpty,
#[msg("Not enough tokens.")]
NotEnoughTokens,
#[msg("Invalid timestamp.")]
InvalidTimestamp,
#[msg("Invalid max claim fee.")]
InvalidMaxClaimFee,
#[msg("Max annual rewards rate exceeded.")]
MaxAnnualRewardsRateExceeded,
#[msg("Rewarder is paused.")]
Paused,
#[msg("Rewards earned exceeded quarry's upper bound.")]
UpperboundExceeded,
}