use anchor_lang::prelude::*;
use anchor_lang::solana_program;
use anchor_spl::token::{Mint, Token, TokenAccount};
use crate_token::CrateToken;
use vipers::*;
mod events;
mod ixs;
mod macros;
mod state;
pub use events::*;
pub use state::*;
pub static FEE_OWNER: Pubkey =
static_pubkey::static_pubkey!("2DDSpDyRbu9gZbcp2JCq2ZaA9FrCzXzoiyiGLyUFYSP5");
pub const FEE_OWNER_BUMP: u8 = 255;
pub const EXERCISE_FEE_KBPS: u64 = 1_000;
declare_id!("TRXf3r361YRfV6Zktov3nvdEqJwAuCowkjh4PUUBYEc");
#[program]
pub mod traction {
use super::*;
#[access_control(ctx.accounts.validate())]
pub fn new_contract(
ctx: Context<NewContract>,
strike: u64,
expiry_ts: i64,
is_put: bool,
contract_bump: u8,
crate_bump: u8,
) -> ProgramResult {
ctx.accounts
.new_contract(strike, expiry_ts, is_put, contract_bump, crate_bump)
}
#[access_control(ctx.accounts.validate())]
pub fn option_write(ctx: Context<OptionWrite>, write_amount: u64) -> ProgramResult {
ctx.accounts.write(write_amount)
}
#[access_control(ctx.accounts.validate())]
pub fn option_exercise(ctx: Context<OptionExercise>, option_amount: u64) -> ProgramResult {
ctx.accounts.exercise(option_amount)
}
#[access_control(ctx.accounts.validate())]
pub fn option_redeem(ctx: Context<OptionRedeem>, writer_amount: u64) -> ProgramResult {
ctx.accounts.redeem(writer_amount)
}
}
#[derive(Accounts)]
#[instruction(
strike: u64,
expiry_ts: u64,
is_put: bool,
contract_bump: u8
)]
pub struct NewContract<'info> {
#[account(
init,
seeds = [
b"OptionsContract" as &[u8],
underlying_mint.key().to_bytes().as_ref(),
quote_mint.key().to_bytes().as_ref(),
strike.to_le_bytes().as_ref(),
expiry_ts.to_le_bytes().as_ref(),
(if is_put { &[1_u8] } else { &[0_u8] })
],
bump = contract_bump,
payer = payer
)]
pub contract: Account<'info, OptionsContract>,
pub underlying_mint: Account<'info, Mint>,
pub quote_mint: Account<'info, Mint>,
pub writer_crate: WriterCrate<'info>,
pub option_mint: Account<'info, Mint>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct WriterCrate<'info> {
pub crate_mint: Account<'info, Mint>,
#[account(mut)]
pub crate_token: UncheckedAccount<'info>,
pub crate_token_program: Program<'info, crate_token::program::CrateToken>,
}
#[derive(Accounts)]
pub struct OptionWrite<'info> {
#[account(mut)]
pub writer_authority: Signer<'info>,
pub contract: Box<Account<'info, OptionsContract>>,
#[account(mut)]
pub user_collateral_funding_tokens: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub option_token_destination: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub crate_collateral_tokens: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub writer_token_destination: Box<Account<'info, TokenAccount>>,
pub writer_crate_token: Box<Account<'info, CrateToken>>,
#[account(mut)]
pub writer_mint: Box<Account<'info, Mint>>,
#[account(mut)]
pub option_mint: Box<Account<'info, Mint>>,
pub token_program: Program<'info, Token>,
pub crate_token_program: Program<'info, crate_token::program::CrateToken>,
}
#[derive(Accounts)]
pub struct OptionExercise<'info> {
pub exerciser_authority: Signer<'info>,
pub contract: Box<Account<'info, OptionsContract>>,
#[account(mut)]
pub exercise_token_source: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub option_mint: Box<Account<'info, Mint>>,
#[account(mut)]
pub option_token_source: Box<Account<'info, TokenAccount>>,
pub writer_crate_token: Box<Account<'info, CrateToken>>,
#[account(mut)]
pub crate_collateral_tokens: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub crate_exercise_tokens: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub collateral_token_destination: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub exercise_fee_destination: Box<Account<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
pub crate_token_program: Program<'info, crate_token::program::CrateToken>,
}
#[derive(Accounts)]
pub struct OptionRedeem<'info> {
#[account(mut)]
pub writer_authority: Signer<'info>,
pub contract: Box<Account<'info, OptionsContract>>,
#[account(mut)]
pub writer_token_source: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub writer_mint: Box<Account<'info, Mint>>,
#[account(mut)]
pub underlying_token_destination: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub quote_token_destination: Box<Account<'info, TokenAccount>>,
pub writer_crate_token: Box<Account<'info, CrateToken>>,
#[account(mut)]
pub crate_collateral_tokens: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub crate_exercise_tokens: Box<Account<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
pub crate_token_program: Program<'info, crate_token::program::CrateToken>,
}
#[error]
pub enum ErrorCode {
#[msg("Unauthorized.")]
Unauthorized,
#[msg("Insufficient collateral to write options.")]
InsufficientCollateral,
#[msg("Options contract is expired.")]
ContractExpired,
#[msg("Cannot redeem until contract expiry.")]
ContractNotYetExpired,
#[msg("A writer mint must have the same decimals as the underlying.")]
WriterDecimalMismatch,
#[msg("An option mint must have the same decimals as the underlying.")]
OptionDecimalMismatch,
#[msg("The underlying and quote mints should not match.")]
UselessMints,
#[msg("Option mint must have zero supply.")]
OptionMintMustHaveZeroSupply,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fee_owner_address() {
let (key, bump) = Pubkey::find_program_address(&[b"TractionDAOFees"], &crate::ID);
assert_eq!(key, FEE_OWNER);
assert_eq!(bump, FEE_OWNER_BUMP);
}
}