├── README.md ├── client ├── __init__.py ├── binary_option.py ├── requirements.txt └── test.py └── program ├── Cargo.lock ├── Cargo.toml ├── program_id.md ├── scripts ├── patch.crates-io.sh └── update-solana-dependencies.sh └── src ├── entrypoint.rs ├── error.rs ├── instruction.rs ├── lib.rs ├── processor.rs ├── spl_utils.rs ├── state.rs ├── system_utils.rs └── validation_utils.rs /README.md: -------------------------------------------------------------------------------- 1 | # Binary Option 2 | 3 | This protocol is a primitive version of a binary options. Participants can enter a long or short position depending on their conviction. (These sides are set completely arbitrarily). The eventual goal is to have a higher level program manage the pool and handle settlements with an oracle based voting approach. Every bet in the pool involves 2 parties (1 long and 1 short) each depositing some collateral. Because bets are made on binary event, the sum of collateral will be equal to a mutiple of some power of 10 (`10 ** N` where `N` is configurable). 4 | 5 | The module contains the Rust implementation of protocol as well as a Python client and test suite. 6 | 7 | Suppose we had a binary option on the winner of the 2021 NBA Finals (Phoenix Suns vs. Milwaulkee Bucks). At the time of writing this (July 9th, 2021), the moneyline spread is -190 Suns +170 Bucks. This backs out an implied probability of approximately 36% that the Bucks win the championship. Suppose our binary option was on the Bucks winning this series, and that it is denominated by some wrapped stablecoin WUSD (dollar pegged) where every contract settled to 10000 WUSD (`N = 4` corresponding to 1 cent granualrity). You observe that someone is willing to go short Bucks for 10 contracts at 3000 WUSD (less than the estimated probability of 36%). You can take on the opposite trade by buying 10 long contracts on the Bucks for 3000. 8 | 9 | This invokes a `Trade` instruction with size 10, buy_price 3000, and sell_price 7000. Note that these prices must sum to 10000. As part of the protocol, you transfer 30000 WUSD into the binary option and the counterparty deposits 70000 (assuming that both parties start with 0 position). In return, 10 long tokens (minted by the contract) are added to your account, and 10 short tokens are minted to your counterparty's account. 10 | 11 | Now suppose the Bucks win Game 3, and the estimated probablity of the Bucks winning the series jumps to 40%. You offer to sell all of your contracts for 4000 WUSD, and you get a buyer. Because you already hold long tokens, the contract will burn those existing tokens, and you are transfered 40000 WUSD from the pool. If your counterparty is currently short 1 contract, they pull out 6000 WUSD from the pool (exiting out of their short position) and deposit 9 * 4000 = 36000 WUSD (buying into their long position). In total, the pool collateral changes by -40000 + 36000 - 6000 = -10000 WUSD or exactly 1 contract! After the dust settles, you walk away with no position and a net profit of 10000 WUSD ($100). 12 | 13 | We'll discuss this mechanism in more detail later. 14 | 15 | ## Client Setup  16 | First, clone down the repository (TODO publish to PyPI) 17 | 18 | Create a virtual environment and and install the dependencies in `client/requirements.txt` 19 | 20 | ``` 21 | python3 -m virtualenv venv 22 | source venv/bin/activate 23 | pip install -r client/requirements.txt 24 | ``` 25 | 26 | To run the tests against the program code deployed on devnet, run: 27 | ``` 28 | python -m client.test 29 | ``` 30 | 31 | # Instructions 32 | 33 | ### InitializeBinaryOption 34 | `InitializeBinaryOption` creates a new binary option where the denominated decimals are specified as arguments. (The "escrow" mint is included in the list of accounts). New mints are created for long and short tokens, and the ownership of these mints is transferred to a program derived address. 35 | 36 | ### Trade 37 | `Trade` handles all of the complicated wiring of a wager being added to the pool. This is tricky because the existing positions of the participants needs to be accounted for. There are 3 variables we care about: 38 | 39 | `n` the number of contracts traded 40 | 41 | `n_b` the number of short contracts owned by the buyer 42 | 43 | `n_s` the number of long contracts owned by the seller 44 | 45 | We know from our college combanatorics/discrete math class that there are 3! = 6 ways to order 3 items. Let's list out all configurations of how these numbers can bet ordered from largest to smallest (assuming all distinct): 46 | 47 | ``` 48 | 1) n_b > n_s > n 49 | 2) n_s > n_b > n 50 | 3) n > n_b > n_s 51 | 4) n > n_s > n_b 52 | 5) n_b > n > n_s 53 | 6) n_s > n > n_b 54 | ``` 55 | This is a lot of cases to consider, but we can group them into combined categories: 56 | ``` 57 | n_b >= n && n_s >= n 58 | ``` 59 | This clause essentially groups 1) and 2) together. In this case, both buyer and seller are simply reducing their existing inventory. Therefore, we can just remove `n` long tokens and `n` short tokens from circulation. Both parties are also entitled to the locked up funds for their positions that were closed, so the buyer receives `n * sell_price` and the seller received `n * buy_price`. This might be confusing at first, but a good way to think about this is that there are no "sellers". Everyone with inventory is a "buyer". If an event as a probability `p` of occuring, the buyer is paying `p` and the seller is paying `1-p`. When a market participant receives funds, they are "selling out" (either locking in profits or losses) of their existing position. 60 | 61 | ``` 62 | n_b < n && n_s < n 63 | ``` 64 | This clause groups 2) and 3) together (most complex). In this case, both buyer and seller swap positions -- the buyer goes from short to long and the seller goes from long to short. We will first burn the tokens all exiting tokens for parties and then mint new tokens to ensure the buyer's change is `+n` and the seller's change is `-n`. Both parties are also entitled to the locked up funds for their positions that were closed (`n_b * sell_price` for the buyer and `n_s * buy_price` for the seller). The net change in tokens can be calculated as follows: `(-n_b - n_s + 2n - n_b - n_s) / 2 = n - n_b - n_s`. If this quantity is positive, this means that the trade causes a net increase in the total supply of contracts in the binary option. Otherwise, it results in a net decrease in total circulation. 65 | 66 | ``` 67 | n_b >= n && n_s < n 68 | ``` 69 | This is equivalent to clause 5. The buyer has swapped positions, and the seller has reduced inventory. Like before, we will burn and mint tokens such the buyer's net change in position is `+n` and the seller's net change is `-n`. Both parties are also entitled to the locked up funds for their positions that were closed. The net change in tokens can be calculated as follows: `(-n - n_s + n - n_s) / 2 = -n_s`. This always results in a decrease in total circulation. 70 | 71 | ``` 72 | n_b < n && n_s >= n 73 | ``` 74 | It's easy to see that this is almost identical to the previous case. The net circulation decreases by `n_b`. This proof is left as an exercise to the reader. 75 | 76 | When all of the dust settles, the pool participants can enter and exit their positions while the pool is live, and the pool will always be fully collateralized! 77 | 78 | ### Settle 79 | `Settle` is invoked when a winner of the bet is decided. This, in theory, should be done through an oracle by the higher level protocol that uses this primative (composability effects). Once an event is settled, no more trades can occur. One TODO is to potentially add another stage -- first stop trading and settle as a gradual process 80 | 81 | ### Collect 82 | `Collect` is invoked when retrieving funds from a pool after it has fully settled. All of the user's tokens are burned and if they have any of the winning token, the user will receive a proportional stake of the pool (`(# tokens / total circulation) * size of pool`). The circulation of the pool is then reduced to reflect a global change in stake of all participants who have yet to retrieve their funds. 83 | -------------------------------------------------------------------------------- /client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarry-xiao/binary-option/c17c8078555431a28fc59cf36831dc7b5dfe05cd/client/__init__.py -------------------------------------------------------------------------------- /client/binary_option.py: -------------------------------------------------------------------------------- 1 | import json 2 | from http import HTTPStatus 3 | from cryptography.fernet import Fernet 4 | import base64 5 | import base58 6 | import struct 7 | 8 | from solana.publickey import PublicKey 9 | from solana.transaction import Transaction, AccountMeta, TransactionInstruction 10 | from solana.account import Account 11 | from solana.rpc.api import Client 12 | import solana.rpc.types as types 13 | from solana.system_program import transfer, TransferParams 14 | from spl.token._layouts import MINT_LAYOUT, ACCOUNT_LAYOUT 15 | from spl.token.instructions import ( 16 | get_associated_token_address, create_associated_token_account, 17 | mint_to, MintToParams, 18 | ) 19 | 20 | SYSTEM_PROGRAM_ID = '11111111111111111111111111111111' 21 | SYSVAR_RENT_ID = 'SysvarRent111111111111111111111111111111111' 22 | ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' 23 | TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' 24 | BINARY_OPTION_PROGRAM_ID = 'betw959P4WToez4DkuXwNsJszqbpe3HuY56AcG5yevx' 25 | 26 | 27 | def initialize_binary_option_instruction( 28 | pool_account, 29 | escrow_mint_account, 30 | escrow_account, 31 | long_token_mint_account, 32 | short_token_mint_account, 33 | mint_authority_account, 34 | update_authority_account, 35 | token_account, 36 | system_account, 37 | rent_account, 38 | decimals 39 | ): 40 | keys = [ 41 | AccountMeta(pubkey=pool_account, is_signer=True, is_writable=True), 42 | AccountMeta(pubkey=escrow_mint_account, is_signer=False, is_writable=False), 43 | AccountMeta(pubkey=escrow_account, is_signer=True, is_writable=True), 44 | AccountMeta(pubkey=long_token_mint_account, is_signer=True, is_writable=False), 45 | AccountMeta(pubkey=short_token_mint_account, is_signer=True, is_writable=False), 46 | AccountMeta(pubkey=mint_authority_account, is_signer=True, is_writable=False), 47 | AccountMeta(pubkey=update_authority_account, is_signer=True, is_writable=False), 48 | AccountMeta(pubkey=token_account, is_signer=False, is_writable=False), 49 | AccountMeta(pubkey=system_account, is_signer=False, is_writable=False), 50 | AccountMeta(pubkey=rent_account, is_signer=False, is_writable=False), 51 | ] 52 | data = struct.pack("" 11 | exit 1 12 | fi 13 | 14 | workspace_crates=( 15 | "$here"/../Cargo.toml 16 | ) 17 | 18 | if [[ ! -r "$solana_dir"/scripts/read-cargo-variable.sh ]]; then 19 | echo "$solana_dir is not a path to the solana monorepo" 20 | exit 1 21 | fi 22 | 23 | set -e 24 | 25 | solana_dir=$(cd "$solana_dir" && pwd) 26 | 27 | source "$solana_dir"/scripts/read-cargo-variable.sh 28 | solana_ver=$(readCargoVariable version "$solana_dir"/sdk/Cargo.toml) 29 | 30 | echo "Patching in $solana_ver from $solana_dir" 31 | 32 | if ! git diff --quiet && [[ -z $DIRTY_OK ]]; then 33 | echo "Error: dirty tree" 34 | exit 1 35 | fi 36 | export DIRTY_OK=1 37 | 38 | for crate in "${workspace_crates[@]}"; do 39 | if grep -q '\[patch.crates-io\]' "$crate"; then 40 | echo "* $crate is already patched" 41 | else 42 | echo "* patched $crate" 43 | cat >> "$crate" <" 11 | exit 1 12 | fi 13 | 14 | if [[ $solana_ver =~ ^v ]]; then 15 | # Drop `v` from v1.2.3... 16 | solana_ver=${solana_ver:1} 17 | fi 18 | 19 | cd "$here"/.. 20 | 21 | echo "Updating Solana version to $solana_ver in $PWD" 22 | 23 | if ! git diff --quiet && [[ -z $DIRTY_OK ]]; then 24 | echo "Error: dirty tree" 25 | exit 1 26 | fi 27 | 28 | declare tomls=() 29 | while IFS='' read -r line; do tomls+=("$line"); done < <(find . -name Cargo.toml) 30 | 31 | crates=( 32 | solana-clap-utils 33 | solana-cli-config 34 | solana-client 35 | solana-logger 36 | solana-program 37 | solana-program-test 38 | solana-remote-wallet 39 | solana-sdk 40 | solana-validator 41 | ) 42 | 43 | set -x 44 | for crate in "${crates[@]}"; do 45 | sed -i -e "s#\(${crate} = \"\).*\(\"\)#\1$solana_ver\2#g" "${tomls[@]}" 46 | done 47 | 48 | -------------------------------------------------------------------------------- /program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))] 2 | 3 | use solana_program::{ 4 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, 5 | }; 6 | 7 | use crate::processor::Processor; 8 | 9 | entrypoint!(process_instruction); 10 | fn process_instruction( 11 | program_id: &Pubkey, 12 | accounts: &[AccountInfo], 13 | instruction_data: &[u8], 14 | ) -> ProgramResult { 15 | Processor::process(program_id, accounts, instruction_data) 16 | } 17 | -------------------------------------------------------------------------------- /program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use solana_program::program_error::ProgramError; 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum BinaryOptionError { 7 | #[error("PublicKeyMismatch")] 8 | PublicKeyMismatch, 9 | #[error("InvalidMintAuthority")] 10 | InvalidMintAuthority, 11 | #[error("NotMintAuthority")] 12 | NotMintAuthority, 13 | #[error("InvalidSupply")] 14 | InvalidSupply, 15 | #[error("InvalidWinner")] 16 | InvalidWinner, 17 | #[error("UninitializedAccount")] 18 | UninitializedAccount, 19 | #[error("IncorrectOwner")] 20 | IncorrectOwner, 21 | #[error("AlreadySettled")] 22 | AlreadySettled, 23 | #[error("BetNotSettled")] 24 | BetNotSettled, 25 | #[error("TokenNotFoundInPool")] 26 | TokenNotFoundInPool, 27 | #[error("PublicKeysShouldBeUnique")] 28 | PublicKeysShouldBeUnique, 29 | #[error("TradePricesIncorrect")] 30 | TradePricesIncorrect, 31 | } 32 | 33 | impl From for ProgramError { 34 | fn from(e: BinaryOptionError) -> Self { 35 | ProgramError::Custom(e as u32) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | instruction::{AccountMeta, Instruction}, 3 | pubkey::Pubkey, 4 | sysvar, 5 | }; 6 | 7 | use borsh::{BorshDeserialize, BorshSerialize}; 8 | 9 | #[repr(C)] 10 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] 11 | pub struct InitializeBinaryOptionArgs { 12 | pub decimals: u8, 13 | } 14 | 15 | #[repr(C)] 16 | #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)] 17 | pub struct TradeArgs { 18 | pub size: u64, 19 | pub buy_price: u64, 20 | pub sell_price: u64, 21 | } 22 | 23 | #[derive(BorshSerialize, BorshDeserialize, Clone)] 24 | pub enum BinaryOptionInstruction { 25 | // TODO: Add comments here 26 | InitializeBinaryOption(InitializeBinaryOptionArgs), 27 | 28 | Trade(TradeArgs), 29 | 30 | Settle, 31 | 32 | Collect, 33 | } 34 | 35 | /// Creates an InitializeBinaryOption instruction 36 | #[allow(clippy::too_many_arguments)] 37 | pub fn initialize_binary_option( 38 | program_id: Pubkey, 39 | pool_account: Pubkey, 40 | escrow_mint: Pubkey, 41 | escrow_account: Pubkey, 42 | long_token_mint: Pubkey, 43 | short_token_mint: Pubkey, 44 | mint_authority: Pubkey, 45 | update_authority: Pubkey, 46 | decimals: u8, 47 | ) -> Instruction { 48 | Instruction { 49 | program_id, 50 | accounts: vec![ 51 | AccountMeta::new(pool_account, true), 52 | AccountMeta::new_readonly(escrow_mint, false), 53 | AccountMeta::new(escrow_account, true), 54 | AccountMeta::new_readonly(long_token_mint, true), 55 | AccountMeta::new_readonly(short_token_mint, true), 56 | AccountMeta::new_readonly(mint_authority, true), 57 | AccountMeta::new_readonly(update_authority, true), 58 | AccountMeta::new_readonly(spl_token::id(), false), 59 | AccountMeta::new_readonly(solana_program::system_program::id(), false), 60 | AccountMeta::new_readonly(sysvar::rent::id(), false), 61 | ], 62 | data: BinaryOptionInstruction::InitializeBinaryOption(InitializeBinaryOptionArgs { 63 | decimals, 64 | }) 65 | .try_to_vec() 66 | .unwrap(), 67 | } 68 | } 69 | 70 | /// Creates a Trade instruction 71 | #[allow(clippy::too_many_arguments)] 72 | pub fn trade( 73 | program_id: Pubkey, 74 | pool_account: Pubkey, 75 | escrow_account: Pubkey, 76 | long_token_mint: Pubkey, 77 | short_token_mint: Pubkey, 78 | buyer: Pubkey, 79 | seller: Pubkey, 80 | buyer_account: Pubkey, 81 | seller_account: Pubkey, 82 | buyer_long_token_account: Pubkey, 83 | buyer_short_token_account: Pubkey, 84 | seller_long_token_account: Pubkey, 85 | seller_short_token_account: Pubkey, 86 | escrow_authority: Pubkey, 87 | size: u64, 88 | buy_price: u64, 89 | sell_price: u64, 90 | ) -> Instruction { 91 | Instruction { 92 | program_id, 93 | accounts: vec![ 94 | AccountMeta::new(pool_account, false), 95 | AccountMeta::new(escrow_account, false), 96 | AccountMeta::new(long_token_mint, false), 97 | AccountMeta::new(short_token_mint, false), 98 | AccountMeta::new_readonly(buyer, true), 99 | AccountMeta::new_readonly(seller, true), 100 | AccountMeta::new(buyer_account, false), 101 | AccountMeta::new(seller_account, false), 102 | AccountMeta::new(buyer_long_token_account, false), 103 | AccountMeta::new(buyer_short_token_account, false), 104 | AccountMeta::new(seller_long_token_account, false), 105 | AccountMeta::new(seller_short_token_account, false), 106 | AccountMeta::new_readonly(escrow_authority, false), 107 | AccountMeta::new_readonly(spl_token::id(), false), 108 | ], 109 | data: BinaryOptionInstruction::Trade(TradeArgs { 110 | size, 111 | buy_price, 112 | sell_price, 113 | }) 114 | .try_to_vec() 115 | .unwrap(), 116 | } 117 | } 118 | 119 | /// Creates a Settle instruction 120 | pub fn settle( 121 | program_id: Pubkey, 122 | pool_account: Pubkey, 123 | winning_mint: Pubkey, 124 | pool_authority: Pubkey, 125 | ) -> Instruction { 126 | Instruction { 127 | program_id, 128 | accounts: vec![ 129 | AccountMeta::new(pool_account, false), 130 | AccountMeta::new_readonly(winning_mint, false), 131 | AccountMeta::new_readonly(pool_authority, true), 132 | ], 133 | data: BinaryOptionInstruction::Settle.try_to_vec().unwrap(), 134 | } 135 | } 136 | 137 | /// Create a Collect instruction 138 | #[allow(clippy::too_many_arguments)] 139 | pub fn collect( 140 | program_id: Pubkey, 141 | pool_account: Pubkey, 142 | collector_account: Pubkey, 143 | collector_long_token_account: Pubkey, 144 | collector_short_token_account: Pubkey, 145 | collector_collateral_account: Pubkey, 146 | long_token_mint_account: Pubkey, 147 | short_token_mint_account: Pubkey, 148 | escrow_account: Pubkey, 149 | escrow_authority_account: Pubkey, 150 | fee_payer_account: Pubkey, 151 | ) -> Instruction { 152 | Instruction { 153 | program_id, 154 | accounts: vec![ 155 | AccountMeta::new(pool_account, false), 156 | AccountMeta::new_readonly(collector_account, false), 157 | AccountMeta::new(collector_long_token_account, false), 158 | AccountMeta::new(collector_short_token_account, false), 159 | AccountMeta::new(collector_collateral_account, false), 160 | AccountMeta::new(long_token_mint_account, false), 161 | AccountMeta::new(short_token_mint_account, false), 162 | AccountMeta::new(escrow_account, false), 163 | AccountMeta::new(escrow_authority_account, false), 164 | AccountMeta::new_readonly(fee_payer_account, true), 165 | AccountMeta::new_readonly(spl_token::id(), false), 166 | AccountMeta::new_readonly(solana_program::system_program::id(), false), 167 | AccountMeta::new_readonly(sysvar::rent::id(), false), 168 | ], 169 | data: BinaryOptionInstruction::Collect.try_to_vec().unwrap(), 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod spl_utils; 6 | pub mod state; 7 | pub mod system_utils; 8 | pub mod validation_utils; 9 | // Export current sdk types for downstream users building with a different sdk version 10 | pub use solana_program; 11 | 12 | solana_program::declare_id!("betw959P4WToez4DkuXwNsJszqbpe3HuY56AcG5yevx"); 13 | -------------------------------------------------------------------------------- /program/src/processor.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::BinaryOptionError, 3 | instruction::BinaryOptionInstruction, 4 | spl_utils::{ 5 | spl_approve, spl_burn, spl_burn_signed, spl_initialize, spl_mint_initialize, spl_mint_to, 6 | spl_set_authority, spl_token_transfer, spl_token_transfer_signed, 7 | }, 8 | state::BinaryOption, 9 | system_utils::{create_new_account, create_or_allocate_account_raw}, 10 | validation_utils::{ 11 | assert_initialized, assert_keys_equal, assert_keys_unequal, assert_owned_by, 12 | }, 13 | }; 14 | use borsh::{BorshDeserialize, BorshSerialize}; 15 | use solana_program::{ 16 | account_info::{next_account_info, AccountInfo}, 17 | entrypoint::ProgramResult, 18 | msg, 19 | program_error::ProgramError, 20 | program_pack::Pack, 21 | pubkey::Pubkey, 22 | }; 23 | use spl_token::{ 24 | instruction::AuthorityType, 25 | state::{Account, Mint}, 26 | }; 27 | 28 | pub struct Processor; 29 | impl Processor { 30 | pub fn process( 31 | program_id: &Pubkey, 32 | accounts: &[AccountInfo], 33 | instruction_data: &[u8], 34 | ) -> ProgramResult { 35 | let instruction = BinaryOptionInstruction::try_from_slice(instruction_data)?; 36 | match instruction { 37 | BinaryOptionInstruction::InitializeBinaryOption(args) => { 38 | msg!("Instruction: InitializeBinaryOption"); 39 | process_initialize_binary_option(program_id, accounts, args.decimals) 40 | } 41 | BinaryOptionInstruction::Trade(args) => { 42 | msg!("Instruction: Trade"); 43 | process_trade( 44 | program_id, 45 | accounts, 46 | args.size, 47 | args.buy_price, 48 | args.sell_price, 49 | ) 50 | } 51 | BinaryOptionInstruction::Settle => { 52 | msg!("Instruction: Settle"); 53 | process_settle(program_id, accounts) 54 | } 55 | BinaryOptionInstruction::Collect => { 56 | msg!("Instruction: Collect"); 57 | process_collect(program_id, accounts) 58 | } 59 | } 60 | } 61 | } 62 | 63 | pub fn process_initialize_binary_option( 64 | program_id: &Pubkey, 65 | accounts: &[AccountInfo], 66 | decimals: u8, 67 | ) -> ProgramResult { 68 | let account_info_iter = &mut accounts.iter(); 69 | let binary_option_account_info = next_account_info(account_info_iter)?; 70 | let escrow_mint_info = next_account_info(account_info_iter)?; 71 | let escrow_account_info = next_account_info(account_info_iter)?; 72 | let long_token_mint_info = next_account_info(account_info_iter)?; 73 | let short_token_mint_info = next_account_info(account_info_iter)?; 74 | let mint_authority_info = next_account_info(account_info_iter)?; 75 | let update_authority_info = next_account_info(account_info_iter)?; 76 | let token_program_info = next_account_info(account_info_iter)?; 77 | let system_account_info = next_account_info(account_info_iter)?; 78 | let rent_info = next_account_info(account_info_iter)?; 79 | 80 | create_new_account( 81 | mint_authority_info, 82 | long_token_mint_info, 83 | Mint::LEN, 84 | token_program_info, 85 | rent_info, 86 | )?; 87 | create_new_account( 88 | mint_authority_info, 89 | short_token_mint_info, 90 | Mint::LEN, 91 | token_program_info, 92 | rent_info, 93 | )?; 94 | create_new_account( 95 | update_authority_info, 96 | escrow_account_info, 97 | Account::LEN, 98 | token_program_info, 99 | rent_info, 100 | )?; 101 | spl_mint_initialize( 102 | token_program_info, 103 | long_token_mint_info, 104 | mint_authority_info, 105 | mint_authority_info, 106 | rent_info, 107 | 0, 108 | )?; 109 | spl_mint_initialize( 110 | token_program_info, 111 | short_token_mint_info, 112 | mint_authority_info, 113 | mint_authority_info, 114 | rent_info, 115 | 0, 116 | )?; 117 | spl_initialize( 118 | token_program_info, 119 | escrow_account_info, 120 | escrow_mint_info, 121 | update_authority_info, 122 | rent_info, 123 | )?; 124 | 125 | assert_keys_equal(*token_program_info.key, spl_token::id())?; 126 | 127 | // Transfer ownership of the escrow accounts to a PDA 128 | let (authority_key, _) = Pubkey::find_program_address( 129 | &[ 130 | long_token_mint_info.key.as_ref(), 131 | short_token_mint_info.key.as_ref(), 132 | token_program_info.key.as_ref(), 133 | program_id.as_ref(), 134 | ], 135 | program_id, 136 | ); 137 | spl_set_authority( 138 | token_program_info, 139 | escrow_account_info, 140 | Some(authority_key), 141 | AuthorityType::AccountOwner, 142 | update_authority_info, 143 | )?; 144 | spl_set_authority( 145 | token_program_info, 146 | long_token_mint_info, 147 | Some(authority_key), 148 | AuthorityType::MintTokens, 149 | update_authority_info, 150 | )?; 151 | spl_set_authority( 152 | token_program_info, 153 | short_token_mint_info, 154 | Some(authority_key), 155 | AuthorityType::MintTokens, 156 | update_authority_info, 157 | )?; 158 | 159 | create_or_allocate_account_raw( 160 | *program_id, 161 | binary_option_account_info, 162 | rent_info, 163 | system_account_info, 164 | update_authority_info, 165 | BinaryOption::LEN, 166 | )?; 167 | 168 | let mut binary_option = 169 | BinaryOption::try_from_slice(&binary_option_account_info.data.borrow_mut())?; 170 | binary_option.decimals = decimals; 171 | binary_option.circulation = 0; 172 | binary_option.settled = false; 173 | binary_option.long_mint_account_pubkey = *long_token_mint_info.key; 174 | binary_option.short_mint_account_pubkey = *short_token_mint_info.key; 175 | binary_option.escrow_mint_account_pubkey = *escrow_mint_info.key; 176 | binary_option.escrow_account_pubkey = *escrow_account_info.key; 177 | binary_option.owner = *update_authority_info.key; 178 | binary_option.serialize(&mut *binary_option_account_info.data.borrow_mut())?; 179 | 180 | Ok(()) 181 | } 182 | 183 | pub fn process_trade( 184 | program_id: &Pubkey, 185 | accounts: &[AccountInfo], 186 | size: u64, 187 | buy_price: u64, 188 | sell_price: u64, 189 | ) -> ProgramResult { 190 | let account_info_iter = &mut accounts.iter(); 191 | let binary_option_account_info = next_account_info(account_info_iter)?; 192 | let escrow_account_info = next_account_info(account_info_iter)?; 193 | let long_token_mint_info = next_account_info(account_info_iter)?; 194 | let short_token_mint_info = next_account_info(account_info_iter)?; 195 | let buyer_info = next_account_info(account_info_iter)?; 196 | let seller_info = next_account_info(account_info_iter)?; 197 | let buyer_account_info = next_account_info(account_info_iter)?; 198 | let seller_account_info = next_account_info(account_info_iter)?; 199 | let buyer_long_token_account_info = next_account_info(account_info_iter)?; 200 | let buyer_short_token_account_info = next_account_info(account_info_iter)?; 201 | let seller_long_token_account_info = next_account_info(account_info_iter)?; 202 | let seller_short_token_account_info = next_account_info(account_info_iter)?; 203 | let authority_info = next_account_info(account_info_iter)?; 204 | let token_program_info = next_account_info(account_info_iter)?; 205 | 206 | // Unpack accounts 207 | let long_token_mint: Mint = assert_initialized(long_token_mint_info)?; 208 | let short_token_mint: Mint = assert_initialized(short_token_mint_info)?; 209 | let buyer_long_token_account: Account = assert_initialized(buyer_long_token_account_info)?; 210 | let buyer_short_token_account: Account = assert_initialized(buyer_short_token_account_info)?; 211 | let seller_long_token_account: Account = assert_initialized(seller_long_token_account_info)?; 212 | let seller_short_token_account: Account = assert_initialized(seller_short_token_account_info)?; 213 | let buyer_account: Account = assert_initialized(buyer_account_info)?; 214 | let seller_account: Account = assert_initialized(seller_account_info)?; 215 | let mut binary_option = 216 | BinaryOption::try_from_slice(&binary_option_account_info.data.borrow_mut())?; 217 | 218 | // Get program derived address for escrow 219 | let (authority_key, bump_seed) = Pubkey::find_program_address( 220 | &[ 221 | long_token_mint_info.key.as_ref(), 222 | short_token_mint_info.key.as_ref(), 223 | token_program_info.key.as_ref(), 224 | program_id.as_ref(), 225 | ], 226 | program_id, 227 | ); 228 | let seeds = &[ 229 | long_token_mint_info.key.as_ref(), 230 | short_token_mint_info.key.as_ref(), 231 | token_program_info.key.as_ref(), 232 | program_id.as_ref(), 233 | &[bump_seed], 234 | ]; 235 | 236 | // Validate data 237 | if buy_price + sell_price != u64::pow(10, binary_option.decimals as u32) { 238 | return Err(BinaryOptionError::TradePricesIncorrect.into()); 239 | } 240 | if binary_option.settled { 241 | return Err(BinaryOptionError::AlreadySettled.into()); 242 | } 243 | assert_keys_equal(*token_program_info.key, spl_token::id())?; 244 | assert_keys_unequal(*buyer_info.key, *seller_info.key)?; 245 | assert_keys_equal(*long_token_mint_info.owner, spl_token::id())?; 246 | assert_keys_equal(*short_token_mint_info.owner, spl_token::id())?; 247 | assert_keys_equal(buyer_long_token_account.owner, *buyer_info.key)?; 248 | assert_keys_equal(buyer_short_token_account.owner, *buyer_info.key)?; 249 | assert_keys_equal(seller_long_token_account.owner, *seller_info.key)?; 250 | assert_keys_equal(seller_short_token_account.owner, *seller_info.key)?; 251 | assert_keys_equal(buyer_account.owner, *buyer_info.key)?; 252 | assert_keys_equal(seller_account.owner, *seller_info.key)?; 253 | assert_keys_equal(authority_key, *authority_info.key)?; 254 | assert_keys_equal( 255 | *long_token_mint_info.key, 256 | binary_option.long_mint_account_pubkey, 257 | )?; 258 | assert_keys_equal( 259 | *short_token_mint_info.key, 260 | binary_option.short_mint_account_pubkey, 261 | )?; 262 | assert_keys_equal( 263 | *escrow_account_info.key, 264 | binary_option.escrow_account_pubkey, 265 | )?; 266 | assert_keys_equal( 267 | buyer_long_token_account.mint, 268 | binary_option.long_mint_account_pubkey, 269 | )?; 270 | assert_keys_equal( 271 | buyer_short_token_account.mint, 272 | binary_option.short_mint_account_pubkey, 273 | )?; 274 | assert_keys_equal( 275 | seller_long_token_account.mint, 276 | binary_option.long_mint_account_pubkey, 277 | )?; 278 | assert_keys_equal( 279 | seller_short_token_account.mint, 280 | binary_option.short_mint_account_pubkey, 281 | )?; 282 | assert_keys_equal(buyer_account.mint, binary_option.escrow_mint_account_pubkey)?; 283 | assert_keys_equal( 284 | seller_account.mint, 285 | binary_option.escrow_mint_account_pubkey, 286 | )?; 287 | 288 | let n = size; 289 | let n_b = buyer_short_token_account.amount; 290 | let n_s = seller_long_token_account.amount; 291 | 292 | let mut b_l = buyer_long_token_account.amount; 293 | let mut b_s = n_b; 294 | let mut s_l = n_s; 295 | let mut s_s = seller_short_token_account.amount; 296 | 297 | match [n_b >= n, n_s >= n] { 298 | /* 299 | When n is less than both n_b and n_s, this means that both buyer and seller are simply reducing their existing inventory. 300 | Therefore, we can just remove n long tokens and n short tokens from circulation. Both parties are also entitled to the locked up 301 | funds for their positions that were closed. This always results in a decrease in total circulation. 302 | */ 303 | [true, true] => { 304 | msg!("Case 1"); 305 | spl_burn( 306 | token_program_info, 307 | buyer_short_token_account_info, 308 | short_token_mint_info, 309 | buyer_info, 310 | n, 311 | )?; 312 | spl_burn( 313 | token_program_info, 314 | seller_long_token_account_info, 315 | long_token_mint_info, 316 | seller_info, 317 | n, 318 | )?; 319 | spl_token_transfer_signed( 320 | token_program_info, 321 | escrow_account_info, 322 | buyer_account_info, 323 | authority_info, 324 | n * sell_price, 325 | seeds, 326 | )?; 327 | spl_token_transfer_signed( 328 | token_program_info, 329 | escrow_account_info, 330 | seller_account_info, 331 | authority_info, 332 | n * buy_price, 333 | seeds, 334 | )?; 335 | b_s -= n; 336 | s_l -= n; 337 | binary_option.decrement_supply(n)?; 338 | } 339 | /* 340 | When n is greater than both n_b and n_s, this means that both buyer and seller have put on a position that is different from their 341 | existing position. We will first burn the tokens of representing the opposite position and then mint new tokens to ensure the buyer's 342 | change is +n and the seller's change is -n. Both parties are also entitled to the locked up funds for their positions that were closed. 343 | The net change in tokens can be calculated as follows: (-n_b - n_s + 2n - n_b - n_s) / 2 = n - n_b - n_s. If this quantity is positive, this 344 | means that the trade causes a net increase in the total supply of contracts in the betting pool. Otherwise, it results in a net decrease 345 | in total circulation. 346 | */ 347 | [false, false] => { 348 | msg!("Case 2"); 349 | spl_burn( 350 | token_program_info, 351 | buyer_short_token_account_info, 352 | short_token_mint_info, 353 | buyer_info, 354 | n_b, 355 | )?; 356 | spl_burn( 357 | token_program_info, 358 | seller_long_token_account_info, 359 | long_token_mint_info, 360 | seller_info, 361 | n_s, 362 | )?; 363 | b_s -= n_b; 364 | s_l -= n_s; 365 | spl_mint_to( 366 | token_program_info, 367 | buyer_long_token_account_info, 368 | long_token_mint_info, 369 | authority_info, 370 | n - n_b, 371 | seeds, 372 | )?; 373 | spl_mint_to( 374 | token_program_info, 375 | seller_short_token_account_info, 376 | short_token_mint_info, 377 | authority_info, 378 | n - n_s, 379 | seeds, 380 | )?; 381 | b_l += n - n_b; 382 | s_s += n - n_s; 383 | spl_token_transfer( 384 | token_program_info, 385 | buyer_account_info, 386 | escrow_account_info, 387 | buyer_info, 388 | (n - n_b) * buy_price, 389 | )?; 390 | spl_token_transfer( 391 | token_program_info, 392 | seller_account_info, 393 | escrow_account_info, 394 | seller_info, 395 | (n - n_s) * sell_price, 396 | )?; 397 | spl_token_transfer_signed( 398 | token_program_info, 399 | escrow_account_info, 400 | buyer_account_info, 401 | authority_info, 402 | n_b * sell_price, 403 | seeds, 404 | )?; 405 | spl_token_transfer_signed( 406 | token_program_info, 407 | escrow_account_info, 408 | seller_account_info, 409 | authority_info, 410 | n_s * buy_price, 411 | seeds, 412 | )?; 413 | if n > n_b + n_s { 414 | binary_option.increment_supply(n - n_b - n_s); 415 | } else { 416 | binary_option.decrement_supply(n - n_b - n_s)?; 417 | } 418 | } 419 | /* 420 | When n is greater than n_b but less than n_s, this means that the buyer has put on a position that is different from their 421 | existing position, and the seller has reduced their inventory. We will burn and mint tokens such the buyer's net change in 422 | position is +n and the seller's net change is -n. Both parties are also entitled to the locked up funds for their positions that were closed. 423 | The net change in tokens can be calculated as follows: (-n - n_s + n - n_s) / 2 = -n_s. This always results in a decrease in total 424 | circulation. 425 | */ 426 | [true, false] => { 427 | msg!("Case 3"); 428 | spl_burn( 429 | token_program_info, 430 | buyer_short_token_account_info, 431 | short_token_mint_info, 432 | buyer_info, 433 | n, 434 | )?; 435 | spl_burn( 436 | token_program_info, 437 | seller_long_token_account_info, 438 | long_token_mint_info, 439 | seller_info, 440 | n_s, 441 | )?; 442 | b_s -= n; 443 | s_l -= n_s; 444 | spl_mint_to( 445 | token_program_info, 446 | seller_short_token_account_info, 447 | short_token_mint_info, 448 | authority_info, 449 | n - n_s, 450 | seeds, 451 | )?; 452 | s_s += n - n_s; 453 | spl_token_transfer( 454 | token_program_info, 455 | seller_account_info, 456 | escrow_account_info, 457 | seller_info, 458 | (n - n_s) * sell_price, 459 | )?; 460 | spl_token_transfer_signed( 461 | token_program_info, 462 | escrow_account_info, 463 | seller_account_info, 464 | authority_info, 465 | n_s * buy_price, 466 | seeds, 467 | )?; 468 | spl_token_transfer_signed( 469 | token_program_info, 470 | escrow_account_info, 471 | buyer_account_info, 472 | authority_info, 473 | n * sell_price, 474 | seeds, 475 | )?; 476 | binary_option.decrement_supply(n_s)?; 477 | } 478 | /* 479 | When n is greater than n_s bust less than n_b, this means that the seller has put on a position that is different from their 480 | existing position, and the buyer has reduced their inventory. We will burn and mint tokens such the buyer's net change in 481 | position is +n and the seller's net change is -n. Both parties are also entitled to the locked up funds for their positions that were closed. 482 | The net change in tokens can be calculated as follows: (-n - n_b + n - n_b) / 2 = -n_b. This always results in a decrease in total 483 | circulation. 484 | */ 485 | [false, true] => { 486 | msg!("Case 4"); 487 | spl_burn( 488 | token_program_info, 489 | seller_long_token_account_info, 490 | long_token_mint_info, 491 | seller_info, 492 | n, 493 | )?; 494 | spl_burn( 495 | token_program_info, 496 | buyer_short_token_account_info, 497 | short_token_mint_info, 498 | buyer_info, 499 | n_b, 500 | )?; 501 | b_s -= n_b; 502 | s_l -= n; 503 | spl_mint_to( 504 | token_program_info, 505 | buyer_long_token_account_info, 506 | long_token_mint_info, 507 | authority_info, 508 | n - n_b, 509 | seeds, 510 | )?; 511 | b_l += n - n_b; 512 | spl_token_transfer( 513 | token_program_info, 514 | buyer_account_info, 515 | escrow_account_info, 516 | buyer_info, 517 | (n - n_b) * buy_price, 518 | )?; 519 | spl_token_transfer_signed( 520 | token_program_info, 521 | escrow_account_info, 522 | buyer_account_info, 523 | authority_info, 524 | n_b * sell_price, 525 | seeds, 526 | )?; 527 | spl_token_transfer_signed( 528 | token_program_info, 529 | escrow_account_info, 530 | seller_account_info, 531 | authority_info, 532 | n * buy_price, 533 | seeds, 534 | )?; 535 | binary_option.decrement_supply(n_b)?; 536 | } 537 | } 538 | // Delegate the burn authority to the PDA, so a private key is unnecessary on collection 539 | // This can probably be optimized to reduce the number of instructions needed at some point 540 | spl_approve( 541 | token_program_info, 542 | buyer_long_token_account_info, 543 | long_token_mint_info, 544 | authority_info, 545 | buyer_info, 546 | b_l, 547 | long_token_mint.decimals, 548 | )?; 549 | spl_approve( 550 | token_program_info, 551 | seller_short_token_account_info, 552 | short_token_mint_info, 553 | authority_info, 554 | seller_info, 555 | s_s, 556 | short_token_mint.decimals, 557 | )?; 558 | spl_approve( 559 | token_program_info, 560 | buyer_short_token_account_info, 561 | short_token_mint_info, 562 | authority_info, 563 | buyer_info, 564 | b_s, 565 | short_token_mint.decimals, 566 | )?; 567 | spl_approve( 568 | token_program_info, 569 | seller_long_token_account_info, 570 | long_token_mint_info, 571 | authority_info, 572 | seller_info, 573 | s_l, 574 | long_token_mint.decimals, 575 | )?; 576 | binary_option.serialize(&mut *binary_option_account_info.data.borrow_mut())?; 577 | Ok(()) 578 | } 579 | 580 | pub fn process_settle(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 581 | // This should NEVER be called directly (otherwise this is literally a rug) 582 | // The `pool_owner_info` needs to approve this action, so the recommended use case is to have a higher 583 | // level program own the pool and use an oracle to resolve settlements 584 | let account_info_iter = &mut accounts.iter(); 585 | let binary_option_account_info = next_account_info(account_info_iter)?; 586 | let winning_mint_account_info = next_account_info(account_info_iter)?; 587 | let pool_owner_info = next_account_info(account_info_iter)?; 588 | 589 | let mut binary_option = 590 | BinaryOption::try_from_slice(&binary_option_account_info.data.borrow_mut())?; 591 | if !pool_owner_info.is_signer { 592 | return Err(ProgramError::MissingRequiredSignature); 593 | } 594 | if binary_option.settled { 595 | return Err(BinaryOptionError::AlreadySettled.into()); 596 | } 597 | 598 | assert_keys_equal(*pool_owner_info.key, binary_option.owner)?; 599 | if *winning_mint_account_info.key == binary_option.long_mint_account_pubkey 600 | || *winning_mint_account_info.key == binary_option.short_mint_account_pubkey 601 | { 602 | binary_option.winning_side_pubkey = *winning_mint_account_info.key; 603 | } else { 604 | return Err(BinaryOptionError::InvalidWinner.into()); 605 | } 606 | binary_option.settled = true; 607 | binary_option.serialize(&mut *binary_option_account_info.data.borrow_mut())?; 608 | Ok(()) 609 | } 610 | 611 | pub fn process_collect(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 612 | let account_info_iter = &mut accounts.iter(); 613 | let binary_option_account_info = next_account_info(account_info_iter)?; 614 | let collector_info = next_account_info(account_info_iter)?; 615 | let collector_long_token_account_info = next_account_info(account_info_iter)?; 616 | let collector_short_token_account_info = next_account_info(account_info_iter)?; 617 | let collector_account_info = next_account_info(account_info_iter)?; 618 | let long_token_mint_info = next_account_info(account_info_iter)?; 619 | let short_token_mint_info = next_account_info(account_info_iter)?; 620 | let escrow_account_info = next_account_info(account_info_iter)?; 621 | let escrow_authority_info = next_account_info(account_info_iter)?; 622 | let token_program_info = next_account_info(account_info_iter)?; 623 | 624 | let collector_long_token_account: Account = 625 | assert_initialized(collector_long_token_account_info)?; 626 | let collector_short_token_account: Account = 627 | assert_initialized(collector_short_token_account_info)?; 628 | let collector_account: Account = assert_initialized(collector_account_info)?; 629 | let escrow_account: Account = assert_initialized(escrow_account_info)?; 630 | let mut binary_option = 631 | BinaryOption::try_from_slice(&binary_option_account_info.data.borrow_mut())?; 632 | 633 | // Get program derived address for escrow 634 | let (escrow_owner_key, bump_seed) = Pubkey::find_program_address( 635 | &[ 636 | long_token_mint_info.key.as_ref(), 637 | short_token_mint_info.key.as_ref(), 638 | token_program_info.key.as_ref(), 639 | program_id.as_ref(), 640 | ], 641 | program_id, 642 | ); 643 | let seeds = &[ 644 | long_token_mint_info.key.as_ref(), 645 | short_token_mint_info.key.as_ref(), 646 | token_program_info.key.as_ref(), 647 | program_id.as_ref(), 648 | &[bump_seed], 649 | ]; 650 | 651 | if !binary_option.settled { 652 | return Err(BinaryOptionError::BetNotSettled.into()); 653 | } 654 | assert_owned_by(long_token_mint_info, &spl_token::id())?; 655 | assert_owned_by(short_token_mint_info, &spl_token::id())?; 656 | assert_keys_equal(collector_long_token_account.owner, *collector_info.key)?; 657 | assert_keys_equal(collector_short_token_account.owner, *collector_info.key)?; 658 | assert_keys_equal(collector_account.owner, *collector_info.key)?; 659 | assert_keys_equal(escrow_owner_key, *escrow_authority_info.key)?; 660 | assert_keys_equal( 661 | *long_token_mint_info.key, 662 | binary_option.long_mint_account_pubkey, 663 | )?; 664 | assert_keys_equal( 665 | *short_token_mint_info.key, 666 | binary_option.short_mint_account_pubkey, 667 | )?; 668 | assert_keys_equal( 669 | *escrow_account_info.key, 670 | binary_option.escrow_account_pubkey, 671 | )?; 672 | assert_keys_equal( 673 | collector_long_token_account.mint, 674 | binary_option.long_mint_account_pubkey, 675 | )?; 676 | assert_keys_equal( 677 | collector_short_token_account.mint, 678 | binary_option.short_mint_account_pubkey, 679 | )?; 680 | assert_keys_equal( 681 | collector_account.mint, 682 | binary_option.escrow_mint_account_pubkey, 683 | )?; 684 | 685 | let reward = if collector_long_token_account.mint == binary_option.winning_side_pubkey { 686 | collector_long_token_account.amount 687 | } else if collector_short_token_account.mint == binary_option.winning_side_pubkey { 688 | collector_short_token_account.amount 689 | } else { 690 | return Err(BinaryOptionError::TokenNotFoundInPool.into()); 691 | }; 692 | 693 | spl_burn_signed( 694 | token_program_info, 695 | collector_long_token_account_info, 696 | long_token_mint_info, 697 | escrow_authority_info, 698 | collector_long_token_account.amount, 699 | seeds, 700 | )?; 701 | spl_burn_signed( 702 | token_program_info, 703 | collector_short_token_account_info, 704 | short_token_mint_info, 705 | escrow_authority_info, 706 | collector_short_token_account.amount, 707 | seeds, 708 | )?; 709 | if reward > 0 { 710 | let amount = (reward * escrow_account.amount) / binary_option.circulation; 711 | spl_token_transfer_signed( 712 | token_program_info, 713 | escrow_account_info, 714 | collector_account_info, 715 | escrow_authority_info, 716 | amount, 717 | seeds, 718 | )?; 719 | binary_option.decrement_supply(reward)?; 720 | } 721 | binary_option.serialize(&mut *binary_option_account_info.data.borrow_mut())?; 722 | Ok(()) 723 | } 724 | -------------------------------------------------------------------------------- /program/src/spl_utils.rs: -------------------------------------------------------------------------------- 1 | use { 2 | solana_program::{ 3 | account_info::AccountInfo, 4 | entrypoint::ProgramResult, 5 | msg, 6 | program::{invoke, invoke_signed}, 7 | pubkey::Pubkey, 8 | }, 9 | spl_token::instruction::{ 10 | approve_checked, burn, initialize_account, initialize_mint, mint_to, set_authority, 11 | transfer, AuthorityType, 12 | }, 13 | }; 14 | 15 | pub fn spl_initialize<'a>( 16 | token_program: &AccountInfo<'a>, 17 | new_account: &AccountInfo<'a>, 18 | mint: &AccountInfo<'a>, 19 | authority: &AccountInfo<'a>, 20 | rent: &AccountInfo<'a>, 21 | ) -> ProgramResult { 22 | let ix = initialize_account(token_program.key, new_account.key, mint.key, authority.key)?; 23 | invoke( 24 | &ix, 25 | &[ 26 | new_account.clone(), 27 | mint.clone(), 28 | authority.clone(), 29 | rent.clone(), 30 | token_program.clone(), 31 | ], 32 | )?; 33 | Ok(()) 34 | } 35 | 36 | pub fn spl_mint_initialize<'a>( 37 | token_program: &AccountInfo<'a>, 38 | mint: &AccountInfo<'a>, 39 | mint_authority: &AccountInfo<'a>, 40 | freeze_authority: &AccountInfo<'a>, 41 | rent_info: &AccountInfo<'a>, 42 | decimals: u8, 43 | ) -> ProgramResult { 44 | let ix = initialize_mint( 45 | token_program.key, 46 | mint.key, 47 | mint_authority.key, 48 | Some(freeze_authority.key), 49 | decimals, 50 | )?; 51 | invoke( 52 | &ix, 53 | &[mint.clone(), rent_info.clone(), token_program.clone()], 54 | )?; 55 | Ok(()) 56 | } 57 | 58 | pub fn spl_approve<'a>( 59 | token_program: &AccountInfo<'a>, 60 | source_account: &AccountInfo<'a>, 61 | mint: &AccountInfo<'a>, 62 | delegate: &AccountInfo<'a>, 63 | owner: &AccountInfo<'a>, 64 | amount: u64, 65 | decimals: u8, 66 | ) -> ProgramResult { 67 | let ix = approve_checked( 68 | token_program.key, 69 | source_account.key, 70 | mint.key, 71 | delegate.key, 72 | owner.key, 73 | &[], 74 | amount, 75 | decimals, 76 | )?; 77 | invoke( 78 | &ix, 79 | &[ 80 | source_account.clone(), 81 | mint.clone(), 82 | delegate.clone(), 83 | owner.clone(), 84 | token_program.clone(), 85 | ], 86 | )?; 87 | Ok(()) 88 | } 89 | 90 | pub fn spl_burn<'a>( 91 | token_program: &AccountInfo<'a>, 92 | burn_account: &AccountInfo<'a>, 93 | mint: &AccountInfo<'a>, 94 | authority: &AccountInfo<'a>, 95 | amount: u64, 96 | ) -> ProgramResult { 97 | if amount > 0 { 98 | let ix = burn( 99 | token_program.key, 100 | burn_account.key, 101 | mint.key, 102 | authority.key, 103 | &[], 104 | amount, 105 | )?; 106 | invoke( 107 | &ix, 108 | &[ 109 | burn_account.clone(), 110 | mint.clone(), 111 | authority.clone(), 112 | token_program.clone(), 113 | ], 114 | )?; 115 | } 116 | Ok(()) 117 | } 118 | 119 | pub fn spl_burn_signed<'a>( 120 | token_program: &AccountInfo<'a>, 121 | burn_account: &AccountInfo<'a>, 122 | mint: &AccountInfo<'a>, 123 | authority: &AccountInfo<'a>, 124 | amount: u64, 125 | signers: &[&[u8]], 126 | ) -> ProgramResult { 127 | msg!("Burn Signed"); 128 | if amount > 0 { 129 | let ix = burn( 130 | token_program.key, 131 | burn_account.key, 132 | mint.key, 133 | authority.key, 134 | &[], 135 | amount, 136 | )?; 137 | invoke_signed( 138 | &ix, 139 | &[ 140 | burn_account.clone(), 141 | mint.clone(), 142 | authority.clone(), 143 | token_program.clone(), 144 | ], 145 | &[signers], 146 | )?; 147 | } 148 | Ok(()) 149 | } 150 | 151 | pub fn spl_mint_to<'a>( 152 | token_program: &AccountInfo<'a>, 153 | dest_account: &AccountInfo<'a>, 154 | mint: &AccountInfo<'a>, 155 | authority: &AccountInfo<'a>, 156 | amount: u64, 157 | signers: &[&[u8]], 158 | ) -> ProgramResult { 159 | let ix = mint_to( 160 | token_program.key, 161 | mint.key, 162 | dest_account.key, 163 | authority.key, 164 | &[], 165 | amount, 166 | )?; 167 | invoke_signed( 168 | &ix, 169 | &[ 170 | mint.clone(), 171 | dest_account.clone(), 172 | authority.clone(), 173 | token_program.clone(), 174 | ], 175 | &[signers], 176 | )?; 177 | Ok(()) 178 | } 179 | 180 | pub fn spl_token_transfer<'a>( 181 | token_program: &AccountInfo<'a>, 182 | source: &AccountInfo<'a>, 183 | destination: &AccountInfo<'a>, 184 | owner: &AccountInfo<'a>, 185 | amount: u64, 186 | ) -> ProgramResult { 187 | if amount > 0 { 188 | let ix = transfer( 189 | token_program.key, 190 | source.key, 191 | destination.key, 192 | owner.key, 193 | &[], 194 | amount, 195 | )?; 196 | invoke( 197 | &ix, 198 | &[ 199 | source.clone(), 200 | destination.clone(), 201 | owner.clone(), 202 | token_program.clone(), 203 | ], 204 | )?; 205 | } 206 | Ok(()) 207 | } 208 | 209 | pub fn spl_token_transfer_signed<'a>( 210 | token_program: &AccountInfo<'a>, 211 | source: &AccountInfo<'a>, 212 | destination: &AccountInfo<'a>, 213 | pda_account: &AccountInfo<'a>, 214 | amount: u64, 215 | signers: &[&[u8]], 216 | ) -> ProgramResult { 217 | if amount > 0 { 218 | let ix = transfer( 219 | token_program.key, 220 | source.key, 221 | destination.key, 222 | pda_account.key, 223 | &[], 224 | amount, 225 | )?; 226 | invoke_signed( 227 | &ix, 228 | &[ 229 | source.clone(), 230 | destination.clone(), 231 | pda_account.clone(), 232 | token_program.clone(), 233 | ], 234 | &[signers], 235 | )?; 236 | } 237 | Ok(()) 238 | } 239 | 240 | pub fn spl_set_authority<'a>( 241 | token_program: &AccountInfo<'a>, 242 | account_to_transfer_ownership: &AccountInfo<'a>, 243 | new_authority: Option, 244 | authority_type: AuthorityType, 245 | owner: &AccountInfo<'a>, 246 | ) -> ProgramResult { 247 | let ix = set_authority( 248 | token_program.key, 249 | account_to_transfer_ownership.key, 250 | new_authority.as_ref(), 251 | authority_type, 252 | owner.key, 253 | &[], 254 | )?; 255 | invoke( 256 | &ix, 257 | &[ 258 | account_to_transfer_ownership.clone(), 259 | owner.clone(), 260 | token_program.clone(), 261 | ], 262 | )?; 263 | Ok(()) 264 | } 265 | -------------------------------------------------------------------------------- /program/src/state.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, 3 | pubkey::Pubkey, 4 | }; 5 | 6 | use crate::error::BinaryOptionError; 7 | use borsh::{BorshDeserialize, BorshSerialize}; 8 | 9 | #[repr(C)] 10 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] 11 | pub struct BinaryOption { 12 | pub decimals: u8, 13 | pub circulation: u64, 14 | pub settled: bool, 15 | pub escrow_mint_account_pubkey: Pubkey, 16 | pub escrow_account_pubkey: Pubkey, 17 | pub long_mint_account_pubkey: Pubkey, 18 | pub short_mint_account_pubkey: Pubkey, 19 | pub owner: Pubkey, 20 | pub winning_side_pubkey: Pubkey, 21 | } 22 | 23 | impl BinaryOption { 24 | pub const LEN: usize = 202; 25 | 26 | pub fn from_account_info(a: &AccountInfo) -> Result { 27 | let binary_option = BinaryOption::try_from_slice(&a.data.borrow_mut())?; 28 | Ok(binary_option) 29 | } 30 | 31 | pub fn increment_supply(&mut self, n: u64) { 32 | self.circulation += n; 33 | } 34 | 35 | pub fn decrement_supply(&mut self, n: u64) -> ProgramResult { 36 | if self.circulation < n { 37 | return Err(BinaryOptionError::InvalidSupply.into()); 38 | } 39 | self.circulation -= n; 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /program/src/system_utils.rs: -------------------------------------------------------------------------------- 1 | use { 2 | solana_program::{ 3 | account_info::AccountInfo, 4 | entrypoint::ProgramResult, 5 | msg, 6 | program::invoke, 7 | pubkey::Pubkey, 8 | system_instruction, 9 | sysvar::{rent::Rent, Sysvar}, 10 | }, 11 | std::convert::TryInto, 12 | }; 13 | 14 | #[inline(always)] 15 | pub fn create_new_account<'a>( 16 | from_info: &AccountInfo<'a>, 17 | new_account_info: &AccountInfo<'a>, 18 | space: usize, 19 | owner_info: &AccountInfo<'a>, 20 | rent_info: &AccountInfo<'a>, 21 | ) -> ProgramResult { 22 | let rent = &Rent::from_account_info(rent_info)?; 23 | let required_lamports = rent 24 | .minimum_balance(space) 25 | .max(1) 26 | .saturating_sub(new_account_info.lamports()); 27 | 28 | msg!("Transfer {} lamports to the new account", required_lamports); 29 | invoke( 30 | &system_instruction::create_account( 31 | from_info.key, 32 | new_account_info.key, 33 | required_lamports, 34 | space as u64, 35 | owner_info.key, 36 | ), 37 | &[from_info.clone(), new_account_info.clone()], 38 | )?; 39 | Ok(()) 40 | } 41 | 42 | #[inline(always)] 43 | pub fn topup<'a>( 44 | account_info: &AccountInfo<'a>, 45 | rent_sysvar_info: &AccountInfo<'a>, 46 | system_program_info: &AccountInfo<'a>, 47 | payer_info: &AccountInfo<'a>, 48 | size: usize, 49 | ) -> ProgramResult { 50 | let rent = &Rent::from_account_info(rent_sysvar_info)?; 51 | let required_lamports = rent 52 | .minimum_balance(size) 53 | .max(1) 54 | .saturating_sub(account_info.lamports()); 55 | 56 | if required_lamports > 0 { 57 | msg!("Transfer {} lamports to the new account", required_lamports); 58 | invoke( 59 | &system_instruction::transfer(payer_info.key, account_info.key, required_lamports), 60 | &[ 61 | payer_info.clone(), 62 | account_info.clone(), 63 | system_program_info.clone(), 64 | ], 65 | )?; 66 | } 67 | Ok(()) 68 | } 69 | 70 | #[inline(always)] 71 | pub fn create_or_allocate_account_raw<'a>( 72 | program_id: Pubkey, 73 | new_account_info: &AccountInfo<'a>, 74 | rent_sysvar_info: &AccountInfo<'a>, 75 | system_program_info: &AccountInfo<'a>, 76 | payer_info: &AccountInfo<'a>, 77 | size: usize, 78 | ) -> ProgramResult { 79 | topup( 80 | new_account_info, 81 | rent_sysvar_info, 82 | system_program_info, 83 | payer_info, 84 | size, 85 | )?; 86 | msg!("Allocate space for the account"); 87 | invoke( 88 | &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()), 89 | &[new_account_info.clone(), system_program_info.clone()], 90 | )?; 91 | 92 | msg!("Assign the account to the owning program"); 93 | invoke( 94 | &system_instruction::assign(new_account_info.key, &program_id), 95 | &[new_account_info.clone(), system_program_info.clone()], 96 | )?; 97 | 98 | Ok(()) 99 | } 100 | -------------------------------------------------------------------------------- /program/src/validation_utils.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::error::BinaryOptionError, 3 | solana_program::{ 4 | account_info::AccountInfo, 5 | entrypoint::ProgramResult, 6 | program_error::ProgramError, 7 | program_pack::{IsInitialized, Pack}, 8 | pubkey::Pubkey, 9 | }, 10 | }; 11 | 12 | pub fn assert_keys_equal(key1: Pubkey, key2: Pubkey) -> ProgramResult { 13 | if key1 != key2 { 14 | Err(BinaryOptionError::PublicKeyMismatch.into()) 15 | } else { 16 | Ok(()) 17 | } 18 | } 19 | 20 | pub fn assert_keys_unequal(key1: Pubkey, key2: Pubkey) -> ProgramResult { 21 | if key1 == key2 { 22 | Err(BinaryOptionError::PublicKeysShouldBeUnique.into()) 23 | } else { 24 | Ok(()) 25 | } 26 | } 27 | 28 | /// assert initialized account 29 | pub fn assert_initialized( 30 | account_info: &AccountInfo, 31 | ) -> Result { 32 | let account: T = T::unpack_unchecked(&account_info.data.borrow())?; 33 | if !account.is_initialized() { 34 | Err(BinaryOptionError::UninitializedAccount.into()) 35 | } else { 36 | Ok(account) 37 | } 38 | } 39 | 40 | /// assert owned by 41 | pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult { 42 | if account.owner != owner { 43 | Err(BinaryOptionError::IncorrectOwner.into()) 44 | } else { 45 | Ok(()) 46 | } 47 | } 48 | --------------------------------------------------------------------------------