anchor/examples/cfo/programs/cfo/src/lib.rs

819 lines
28 KiB
Rust

// WIP. This program has been checkpointed and is not production ready.
use anchor_lang::associated_seeds;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
use anchor_lang::solana_program::{system_instruction, system_program};
use anchor_spl::token::{self, Mint, TokenAccount};
use anchor_spl::{dex, mint};
use registry::{Registrar, RewardVendorKind};
use std::convert::TryInto;
/// CFO is the program representing the Serum chief financial officer. It is
/// the program responsible for collecting and distributing fees from the Serum
/// DEX.
#[program]
pub mod cfo {
use super::*;
/// Creates a financial officer account associated with a DEX program ID.
#[access_control(is_distribution_valid(&d))]
pub fn create_officer(
ctx: Context<CreateOfficer>,
d: Distribution,
registrar: Pubkey,
msrm_registrar: Pubkey,
) -> Result<()> {
let officer = &mut ctx.accounts.officer;
officer.authority = *ctx.accounts.authority.key;
officer.swap_program = *ctx.accounts.swap_program.key;
officer.dex_program = *ctx.accounts.dex_program.key;
officer.distribution = d;
officer.registrar = registrar;
officer.msrm_registrar = msrm_registrar;
officer.stake = *ctx.accounts.stake.to_account_info().key;
officer.treasury = *ctx.accounts.treasury.to_account_info().key;
officer.srm_vault = *ctx.accounts.srm_vault.to_account_info().key;
emit!(OfficerDidCreate {
pubkey: *officer.to_account_info().key,
});
Ok(())
}
/// Creates a deterministic token account owned by the CFO.
/// This should be used when a new mint is used for collecting fees.
/// Can only be called once per token CFO and token mint.
pub fn create_officer_token(_ctx: Context<CreateOfficerToken>) -> Result<()> {
Ok(())
}
/// Updates the cfo's fee distribution.
#[access_control(is_distribution_valid(&d))]
pub fn set_distribution(ctx: Context<SetDistribution>, d: Distribution) -> Result<()> {
ctx.accounts.officer.distribution = d.clone();
emit!(DistributionDidChange { distribution: d });
Ok(())
}
/// Transfers fees from the dex to the CFO.
pub fn sweep_fees<'info>(ctx: Context<'_, '_, '_, 'info, SweepFees<'info>>) -> Result<()> {
let seeds = associated_seeds! {
account = ctx.accounts.officer,
associated = ctx.accounts.dex.dex_program
};
let cpi_ctx: CpiContext<'_, '_, '_, 'info, dex::SweepFees<'info>> = (&*ctx.accounts).into();
dex::sweep_fees(cpi_ctx.with_signer(&[seeds]))?;
Ok(())
}
/// Convert the CFO's entire non-SRM token balance into USDC.
/// Assumes USDC is the quote currency.
#[access_control(is_not_trading(&ctx.accounts.instructions))]
pub fn swap_to_usdc<'info>(
ctx: Context<'_, '_, '_, 'info, SwapToUsdc<'info>>,
min_exchange_rate: ExchangeRate,
) -> Result<()> {
let seeds = associated_seeds! {
account = ctx.accounts.officer,
associated = ctx.accounts.dex_program
};
let cpi_ctx: CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> = (&*ctx.accounts).into();
swap::cpi::swap(
cpi_ctx.with_signer(&[seeds]),
swap::Side::Bid,
token::accessor::amount(&ctx.accounts.from_vault)?,
min_exchange_rate.into(),
)?;
Ok(())
}
/// Convert the CFO's entire token balance into SRM.
/// Assumes SRM is the base currency.
#[access_control(is_not_trading(&ctx.accounts.instructions))]
pub fn swap_to_srm<'info>(
ctx: Context<'_, '_, '_, 'info, SwapToSrm<'info>>,
min_exchange_rate: ExchangeRate,
) -> Result<()> {
let seeds = associated_seeds! {
account = ctx.accounts.officer,
associated = ctx.accounts.dex_program
};
let cpi_ctx: CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> = (&*ctx.accounts).into();
swap::cpi::swap(
cpi_ctx.with_signer(&[seeds]),
swap::Side::Bid,
token::accessor::amount(&ctx.accounts.from_vault)?,
min_exchange_rate.into(),
)?;
Ok(())
}
/// Distributes srm tokens to the various categories. Before calling this,
/// one must convert the fees into SRM via the swap APIs.
#[access_control(is_distribution_ready(&ctx.accounts))]
pub fn distribute<'info>(ctx: Context<'_, '_, '_, 'info, Distribute<'info>>) -> Result<()> {
let total_fees = ctx.accounts.srm_vault.amount;
let seeds = associated_seeds! {
account = ctx.accounts.officer,
associated = ctx.accounts.dex_program
};
// Burn.
let burn_amount: u64 = u128::from(total_fees)
.checked_mul(ctx.accounts.officer.distribution.burn.into())
.unwrap()
.checked_div(100)
.unwrap()
.try_into()
.map_err(|_| ErrorCode::U128CannotConvert)?;
token::burn(ctx.accounts.into_burn().with_signer(&[seeds]), burn_amount)?;
// Stake.
let stake_amount: u64 = u128::from(total_fees)
.checked_mul(ctx.accounts.officer.distribution.stake.into())
.unwrap()
.checked_div(100)
.unwrap()
.try_into()
.map_err(|_| ErrorCode::U128CannotConvert)?;
token::transfer(
ctx.accounts.into_stake_transfer().with_signer(&[seeds]),
stake_amount,
)?;
// Treasury.
let treasury_amount: u64 = u128::from(total_fees)
.checked_mul(ctx.accounts.officer.distribution.treasury.into())
.unwrap()
.checked_div(100)
.unwrap()
.try_into()
.map_err(|_| ErrorCode::U128CannotConvert)?;
token::transfer(
ctx.accounts.into_treasury_transfer().with_signer(&[seeds]),
treasury_amount,
)?;
Ok(())
}
#[access_control(is_stake_reward_ready(&ctx.accounts))]
pub fn drop_stake_reward<'info>(
ctx: Context<'_, '_, '_, 'info, DropStakeReward<'info>>,
) -> Result<()> {
// Common reward parameters.
let expiry_ts = 1853942400; // 9/30/2028.
let expiry_receiver = *ctx.accounts.officer.to_account_info().key;
let locked_kind = {
let start_ts = 1633017600; // 9/30/2021.
let end_ts = 1822320000; // 9/30/2027.
let period_count = 2191;
RewardVendorKind::Locked {
start_ts,
end_ts,
period_count,
}
};
let seeds = associated_seeds! {
account = ctx.accounts.officer,
associated = ctx.accounts.dex_program
};
// Total amount staked denominated in SRM (i.e. MSRM is converted to
// SRM)
let total_pool_value = u128::from(ctx.accounts.srm.pool_mint.supply)
.checked_mul(500)
.unwrap()
.checked_add(
u128::from(ctx.accounts.msrm.pool_mint.supply)
.checked_mul(1_000_000)
.unwrap(),
)
.unwrap();
// Total reward split between both the SRM and MSRM stake pools.
let total_reward_amount = u128::from(ctx.accounts.stake.amount);
// Proportion of the reward going to the srm pool.
//
// total_reward_amount * (srm_pool_value / total_pool_value)
//
let srm_amount: u64 = u128::from(ctx.accounts.srm.pool_mint.supply)
.checked_mul(500)
.unwrap()
.checked_mul(total_reward_amount)
.unwrap()
.checked_div(total_pool_value)
.unwrap()
.try_into()
.map_err(|_| ErrorCode::U128CannotConvert)?;
// Proportion of the reward going to the msrm pool.
//
// total_reward_amount * (msrm_pool_value / total_pool_value)
//
let msrm_amount = u128::from(ctx.accounts.msrm.pool_mint.supply)
.checked_mul(total_reward_amount)
.unwrap()
.checked_div(total_pool_value)
.unwrap()
.try_into()
.map_err(|_| ErrorCode::U128CannotConvert)?;
// SRM drop.
{
// Drop locked reward.
let (_, nonce) = Pubkey::find_program_address(
&[
ctx.accounts.srm.registrar.to_account_info().key.as_ref(),
ctx.accounts.srm.vendor.to_account_info().key.as_ref(),
],
ctx.accounts.token_program.key,
);
registry::cpi::drop_reward(
ctx.accounts.into_srm_reward().with_signer(&[seeds]),
locked_kind.clone(),
srm_amount.try_into().unwrap(),
expiry_ts,
expiry_receiver,
nonce,
)?;
// Drop unlocked reward.
registry::cpi::drop_reward(
ctx.accounts.into_srm_reward().with_signer(&[seeds]),
RewardVendorKind::Unlocked,
srm_amount,
expiry_ts,
expiry_receiver,
nonce,
)?;
}
// MSRM drop.
{
// Drop locked reward.
let (_, nonce) = Pubkey::find_program_address(
&[
ctx.accounts.msrm.registrar.to_account_info().key.as_ref(),
ctx.accounts.msrm.vendor.to_account_info().key.as_ref(),
],
ctx.accounts.token_program.key,
);
registry::cpi::drop_reward(
ctx.accounts.into_msrm_reward().with_signer(&[seeds]),
locked_kind,
msrm_amount,
expiry_ts,
expiry_receiver,
nonce,
)?;
// Drop unlocked reward.
registry::cpi::drop_reward(
ctx.accounts.into_msrm_reward().with_signer(&[seeds]),
RewardVendorKind::Unlocked,
msrm_amount,
expiry_ts,
expiry_receiver,
nonce,
)?;
}
Ok(())
}
}
// Context accounts.
#[derive(Accounts)]
pub struct CreateOfficer<'info> {
#[account(init, associated = dex_program, payer = authority)]
officer: ProgramAccount<'info, Officer>,
#[account(
init,
token = mint,
associated = officer, with = b"vault",
space = TokenAccount::LEN,
payer = authority,
)]
srm_vault: CpiAccount<'info, TokenAccount>,
#[account(
init,
token = mint,
associated = officer, with = b"stake",
space = TokenAccount::LEN,
payer = authority,
)]
stake: CpiAccount<'info, TokenAccount>,
#[account(
init,
token = mint,
associated = officer, with = b"treasury",
space = TokenAccount::LEN,
payer = authority,
)]
treasury: CpiAccount<'info, TokenAccount>,
#[account(signer)]
authority: AccountInfo<'info>,
#[cfg_attr(
not(feature = "test"),
account(address = mint::SRM),
)]
mint: AccountInfo<'info>,
#[account(executable)]
dex_program: AccountInfo<'info>,
#[account(executable)]
swap_program: AccountInfo<'info>,
#[account(address = system_program::ID)]
system_program: AccountInfo<'info>,
#[account(address = spl_token::ID)]
token_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct CreateOfficerToken<'info> {
officer: ProgramAccount<'info, Officer>,
#[account(
init,
token = mint,
associated = officer, with = mint,
space = TokenAccount::LEN,
payer = payer,
)]
token: CpiAccount<'info, TokenAccount>,
#[account(owner = token_program)]
mint: AccountInfo<'info>,
#[account(mut, signer)]
payer: AccountInfo<'info>,
#[account(address = system_program::ID)]
system_program: AccountInfo<'info>,
#[account(address = spl_token::ID)]
token_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct SetDistribution<'info> {
#[account(has_one = authority)]
officer: ProgramAccount<'info, Officer>,
#[account(signer)]
authority: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct SweepFees<'info> {
#[account(associated = dex.dex_program)]
officer: ProgramAccount<'info, Officer>,
#[account(
mut,
owner = dex.token_program,
associated = officer, with = mint,
)]
sweep_vault: AccountInfo<'info>,
mint: AccountInfo<'info>,
dex: Dex<'info>,
}
#[derive(Accounts)]
pub struct Dex<'info> {
#[account(mut)]
market: AccountInfo<'info>,
#[account(mut)]
pc_vault: AccountInfo<'info>,
sweep_authority: AccountInfo<'info>,
vault_signer: AccountInfo<'info>,
dex_program: AccountInfo<'info>,
#[account(address = spl_token::ID)]
token_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct SwapToUsdc<'info> {
#[account(associated = dex_program)]
officer: ProgramAccount<'info, Officer>,
market: DexMarketAccounts<'info>,
#[account(
owner = token_program,
constraint = &officer.treasury != from_vault.key,
constraint = &officer.stake != from_vault.key,
)]
from_vault: AccountInfo<'info>,
#[account(owner = token_program)]
quote_vault: AccountInfo<'info>,
#[account(associated = officer, with = mint::USDC)]
usdc_vault: AccountInfo<'info>,
#[account(address = swap::ID)]
swap_program: AccountInfo<'info>,
#[account(address = dex::ID)]
dex_program: AccountInfo<'info>,
#[account(address = token::ID)]
token_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
#[account(address = tx_instructions::ID)]
instructions: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct SwapToSrm<'info> {
#[account(associated = dex_program)]
officer: ProgramAccount<'info, Officer>,
market: DexMarketAccounts<'info>,
#[account(
owner = token_program,
constraint = &officer.treasury != from_vault.key,
constraint = &officer.stake != from_vault.key,
)]
from_vault: AccountInfo<'info>,
#[account(owner = token_program)]
quote_vault: AccountInfo<'info>,
#[account(
associated = officer,
with = mint::SRM,
constraint = &officer.treasury != from_vault.key,
constraint = &officer.stake != from_vault.key,
)]
srm_vault: AccountInfo<'info>,
#[account(address = swap::ID)]
swap_program: AccountInfo<'info>,
#[account(address = dex::ID)]
dex_program: AccountInfo<'info>,
#[account(address = token::ID)]
token_program: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
#[account(address = tx_instructions::ID)]
instructions: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct DexMarketAccounts<'info> {
#[account(mut)]
market: AccountInfo<'info>,
#[account(mut)]
open_orders: AccountInfo<'info>,
#[account(mut)]
request_queue: AccountInfo<'info>,
#[account(mut)]
event_queue: AccountInfo<'info>,
#[account(mut)]
bids: AccountInfo<'info>,
#[account(mut)]
asks: AccountInfo<'info>,
// The `spl_token::Account` that funds will be taken from, i.e., transferred
// from the user into the market's vault.
//
// For bids, this is the base currency. For asks, the quote.
#[account(mut)]
order_payer_token_account: AccountInfo<'info>,
// Also known as the "base" currency. For a given A/B market,
// this is the vault for the A mint.
#[account(mut)]
coin_vault: AccountInfo<'info>,
// Also known as the "quote" currency. For a given A/B market,
// this is the vault for the B mint.
#[account(mut)]
pc_vault: AccountInfo<'info>,
// PDA owner of the DEX's token accounts for base + quote currencies.
vault_signer: AccountInfo<'info>,
// User wallets.
#[account(mut)]
coin_wallet: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct Distribute<'info> {
#[account(has_one = treasury, has_one = stake)]
officer: ProgramAccount<'info, Officer>,
treasury: AccountInfo<'info>,
stake: AccountInfo<'info>,
#[account(
owner = token_program,
constraint = srm_vault.mint == mint::SRM,
)]
srm_vault: CpiAccount<'info, TokenAccount>,
#[account(address = mint::SRM)]
mint: AccountInfo<'info>,
#[account(address = spl_token::ID)]
token_program: AccountInfo<'info>,
#[account(address = dex::ID)]
dex_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct DropStakeReward<'info> {
#[account(
has_one = stake,
constraint = srm.registrar.key == &officer.registrar,
constraint = msrm.registrar.key == &officer.msrm_registrar,
)]
officer: ProgramAccount<'info, Officer>,
#[account(associated = officer, with = b"stake", with = mint)]
stake: CpiAccount<'info, TokenAccount>,
#[cfg_attr(
not(feature = "test"),
account(address = mint::SRM),
)]
mint: AccountInfo<'info>,
srm: DropStakeRewardPool<'info>,
msrm: DropStakeRewardPool<'info>,
#[account(owner = registry_program)]
msrm_registrar: CpiAccount<'info, Registrar>,
#[account(address = token::ID)]
token_program: AccountInfo<'info>,
#[account(address = registry::ID)]
registry_program: AccountInfo<'info>,
#[account(address = lockup::ID)]
lockup_program: AccountInfo<'info>,
#[account(address = dex::ID)]
dex_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
rent: Sysvar<'info, Rent>,
}
// Don't bother doing validation on the individual accounts. Allow the stake
// program to handle it.
#[derive(Accounts)]
pub struct DropStakeRewardPool<'info> {
registrar: AccountInfo<'info>,
reward_event_q: AccountInfo<'info>,
pool_mint: CpiAccount<'info, Mint>,
vendor: AccountInfo<'info>,
vendor_vault: AccountInfo<'info>,
}
// Accounts.
#[associated]
#[derive(Default)]
pub struct Officer {
// Priviledged account.
pub authority: Pubkey,
// Vault holding the officer's SRM tokens prior to distribution.
pub srm_vault: Pubkey,
// Escrow SRM vault holding tokens which are dropped onto stakers.
pub stake: Pubkey,
// SRM token account to send treasury earned tokens to.
pub treasury: Pubkey,
// Defines the fee distribution, i.e., what percent each fee category gets.
pub distribution: Distribution,
// Swap frontend for the dex.
pub swap_program: Pubkey,
// Dex program the officer is associated with.
pub dex_program: Pubkey,
// SRM stake pool address
pub registrar: Pubkey,
// MSRM stake pool address.
pub msrm_registrar: Pubkey,
}
#[derive(AnchorSerialize, AnchorDeserialize, Default, Clone)]
pub struct Distribution {
burn: u8,
stake: u8,
treasury: u8,
}
// CpiContext transformations.
impl<'info> From<&SweepFees<'info>> for CpiContext<'_, '_, '_, 'info, dex::SweepFees<'info>> {
fn from(sweep: &SweepFees<'info>) -> Self {
let program = sweep.dex.dex_program.to_account_info();
let accounts = dex::SweepFees {
market: sweep.dex.market.to_account_info(),
pc_vault: sweep.dex.pc_vault.to_account_info(),
sweep_authority: sweep.dex.sweep_authority.to_account_info(),
sweep_receiver: sweep.sweep_vault.to_account_info(),
vault_signer: sweep.dex.vault_signer.to_account_info(),
token_program: sweep.dex.token_program.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
impl<'info> From<&SwapToSrm<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> {
fn from(accs: &SwapToSrm<'info>) -> Self {
let program = accs.swap_program.to_account_info();
let accounts = swap::Swap {
market: swap::MarketAccounts {
market: accs.market.market.clone(),
open_orders: accs.market.open_orders.clone(),
request_queue: accs.market.request_queue.clone(),
event_queue: accs.market.event_queue.clone(),
bids: accs.market.bids.clone(),
asks: accs.market.asks.clone(),
order_payer_token_account: accs.market.order_payer_token_account.clone(),
coin_vault: accs.market.coin_vault.clone(),
pc_vault: accs.market.pc_vault.clone(),
vault_signer: accs.market.vault_signer.clone(),
coin_wallet: accs.srm_vault.clone(),
},
authority: accs.officer.to_account_info(),
pc_wallet: accs.from_vault.to_account_info(),
dex_program: accs.dex_program.to_account_info(),
token_program: accs.token_program.to_account_info(),
rent: accs.rent.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
impl<'info> From<&SwapToUsdc<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> {
fn from(accs: &SwapToUsdc<'info>) -> Self {
let program = accs.swap_program.to_account_info();
let accounts = swap::Swap {
market: swap::MarketAccounts {
market: accs.market.market.clone(),
open_orders: accs.market.open_orders.clone(),
request_queue: accs.market.request_queue.clone(),
event_queue: accs.market.event_queue.clone(),
bids: accs.market.bids.clone(),
asks: accs.market.asks.clone(),
order_payer_token_account: accs.market.order_payer_token_account.clone(),
coin_vault: accs.market.coin_vault.clone(),
pc_vault: accs.market.pc_vault.clone(),
vault_signer: accs.market.vault_signer.clone(),
coin_wallet: accs.from_vault.to_account_info(),
},
authority: accs.officer.to_account_info(),
pc_wallet: accs.usdc_vault.clone(),
dex_program: accs.dex_program.to_account_info(),
token_program: accs.token_program.to_account_info(),
rent: accs.rent.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
impl<'info> From<&Distribute<'info>> for CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
fn from(accs: &Distribute<'info>) -> Self {
let program = accs.token_program.to_account_info();
let accounts = token::Burn {
mint: accs.mint.to_account_info(),
to: accs.srm_vault.to_account_info(),
authority: accs.officer.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
impl<'info> DropStakeReward<'info> {
fn into_srm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
let program = self.registry_program.clone();
let accounts = registry::DropReward {
registrar: ProgramAccount::try_from(&self.srm.registrar).unwrap(),
reward_event_q: ProgramAccount::try_from(&self.srm.reward_event_q).unwrap(),
pool_mint: self.srm.pool_mint.clone(),
vendor: ProgramAccount::try_from(&self.srm.vendor).unwrap(),
vendor_vault: CpiAccount::try_from(&self.srm.vendor_vault).unwrap(),
depositor: self.stake.to_account_info(),
depositor_authority: self.officer.to_account_info(),
token_program: self.token_program.clone(),
clock: self.clock.clone(),
rent: self.rent.clone(),
};
CpiContext::new(program, accounts)
}
fn into_msrm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
let program = self.registry_program.clone();
let accounts = registry::DropReward {
registrar: ProgramAccount::try_from(&self.msrm.registrar).unwrap(),
reward_event_q: ProgramAccount::try_from(&self.msrm.reward_event_q).unwrap(),
pool_mint: self.msrm.pool_mint.clone(),
vendor: ProgramAccount::try_from(&self.msrm.vendor).unwrap(),
vendor_vault: CpiAccount::try_from(&self.msrm.vendor_vault).unwrap(),
depositor: self.stake.to_account_info(),
depositor_authority: self.officer.to_account_info(),
token_program: self.token_program.clone(),
clock: self.clock.clone(),
rent: self.rent.clone(),
};
CpiContext::new(program, accounts)
}
}
impl<'info> Distribute<'info> {
fn into_burn(&self) -> CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
let program = self.token_program.clone();
let accounts = token::Burn {
mint: self.mint.clone(),
to: self.srm_vault.to_account_info(),
authority: self.officer.to_account_info(),
};
CpiContext::new(program, accounts)
}
fn into_stake_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
let program = self.token_program.clone();
let accounts = token::Transfer {
from: self.srm_vault.to_account_info(),
to: self.stake.to_account_info(),
authority: self.officer.to_account_info(),
};
CpiContext::new(program, accounts)
}
fn into_treasury_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
let program = self.token_program.clone();
let accounts = token::Transfer {
from: self.srm_vault.to_account_info(),
to: self.treasury.to_account_info(),
authority: self.officer.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
// Events.
#[event]
pub struct DistributionDidChange {
distribution: Distribution,
}
#[event]
pub struct OfficerDidCreate {
pubkey: Pubkey,
}
// Error.
#[error]
pub enum ErrorCode {
#[msg("Distribution does not add to 100")]
InvalidDistribution,
#[msg("u128 cannot be converted into u64")]
U128CannotConvert,
#[msg("Only one instruction is allowed for this transaction")]
TooManyInstructions,
#[msg("Not enough SRM has been accumulated to distribute")]
InsufficientDistributionAmount,
#[msg("Must drop more SRM onto the stake pool")]
InsufficientStakeReward,
}
// Access control.
fn is_distribution_valid(d: &Distribution) -> Result<()> {
if d.burn + d.stake + d.treasury != 100 {
return Err(ErrorCode::InvalidDistribution.into());
}
Ok(())
}
fn is_distribution_ready(accounts: &Distribute) -> Result<()> {
if accounts.srm_vault.amount < 1_000_000 {
return Err(ErrorCode::InsufficientDistributionAmount.into());
}
Ok(())
}
// `ixs` must be the Instructions sysvar.
fn is_not_trading(ixs: &AccountInfo) -> Result<()> {
let data = ixs.try_borrow_data()?;
match tx_instructions::load_instruction_at(1, &data) {
Ok(_) => Err(ErrorCode::TooManyInstructions.into()),
Err(_) => Ok(()),
}
}
fn is_stake_reward_ready(accounts: &DropStakeReward) -> Result<()> {
// Min drop is 15,0000 SRM.
let min_reward: u64 = 15_000_000_000;
if accounts.stake.amount < min_reward {
return Err(ErrorCode::InsufficientStakeReward.into());
}
Ok(())
}
// Redefintions.
//
// The following types are redefined so that they can be parsed into the IDL,
// since Anchor doesn't yet support idl parsing across multiple crates.
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ExchangeRate {
rate: u64,
from_decimals: u8,
quote_decimals: u8,
strict: bool,
}
impl From<ExchangeRate> for swap::ExchangeRate {
fn from(e: ExchangeRate) -> Self {
let ExchangeRate {
rate,
from_decimals,
quote_decimals,
strict,
} = e;
Self {
rate,
from_decimals,
quote_decimals,
strict,
}
}
}