├── Pyth ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ └── state.rs ├── program │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ │ ├── patch.crates-io.sh │ │ └── update-solana-dependencies.sh │ └── src │ │ ├── entrypoint.rs │ │ ├── error.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── processor.rs │ │ └── state.rs └── python │ ├── random_entropy.py │ └── seed_values ├── README.md ├── charity_auction ├── .gitignore ├── client │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ ├── state.rs │ │ └── utils.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ ├── src │ ├── accounts.rs │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ ├── randoms.rs │ ├── state.rs │ └── utils.rs │ └── tests │ └── integration.rs ├── charity_token_launch ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ ├── state.rs │ │ └── utils.rs └── program │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ └── state.rs ├── core ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ └── state.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── entrypoint.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ └── utils.rs ├── ice_cream ├── client │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ └── state.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ └── state.rs ├── options ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── compute_log │ └── src │ │ ├── main.rs │ │ └── state.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── configs │ └── kinobi.cjs │ ├── idl │ └── core.json │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── accounts.rs │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ ├── state.rs │ └── utils.rs ├── random_numbers ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ ├── state.rs │ │ └── utils.rs ├── program │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ │ ├── patch.crates-io.sh │ │ └── update-solana-dependencies.sh │ └── src │ │ ├── entrypoint.rs │ │ ├── error.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── processor.rs │ │ └── state.rs └── python │ └── xorshift.py ├── shorts ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── compute_log │ └── src │ │ ├── main.rs │ │ └── state.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── configs │ └── kinobi.cjs │ ├── idl │ └── core.json │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── accounts.rs │ ├── amm.rs │ ├── amm_state.rs │ ├── core_shorts.rs │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ ├── state.rs │ └── utils.rs ├── solana_streamer ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ └── state.rs ├── program │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ │ ├── patch.crates-io.sh │ │ └── update-solana-dependencies.sh │ └── src │ │ ├── entrypoint.rs │ │ ├── error.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── processor.rs │ │ └── state.rs └── python │ ├── rpc_funcs.py │ ├── sql_funcs.py │ └── streamer.py ├── token_2022 ├── client │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ └── state.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── accounts.rs │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ ├── state.rs │ └── utils.rs ├── transfer_hook ├── client │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── main.rs │ │ └── state.rs └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ ├── patch.crates-io.sh │ └── update-solana-dependencies.sh │ └── src │ ├── accounts.rs │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── lib.rs │ ├── processor.rs │ ├── state.rs │ └── utils.rs ├── twitter_tokens ├── client │ ├── Cargo.lock │ ├── Cargo.toml │ ├── bid_output.txt │ ├── bid_select.sh │ ├── random_input │ ├── select_output.txt │ └── src │ │ ├── main.rs │ │ ├── state.rs │ │ └── utils.rs ├── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── scripts │ │ ├── patch.crates-io.sh │ │ └── update-solana-dependencies.sh │ ├── src │ │ ├── accounts.rs │ │ ├── entrypoint.rs │ │ ├── error.rs │ │ ├── instruction.rs │ │ ├── lib.rs │ │ ├── processor.rs │ │ ├── state.rs │ │ └── utils.rs │ └── tests │ │ └── integration.rs └── python │ ├── .gitignore │ ├── error_codes.py │ ├── log.py │ ├── rpc_funcs.py │ ├── sig_streamer.py │ └── transactions.py └── unity_example └── program ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── scripts ├── patch.crates-io.sh └── update-solana-dependencies.sh └── src ├── entrypoint.rs ├── error.rs ├── instruction.rs ├── lib.rs ├── processor.rs └── state.rs /Pyth/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /Pyth/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" 16 | rand = "0.8.5" -------------------------------------------------------------------------------- /Pyth/client/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod state; 2 | 3 | use std::env; 4 | use std::str::FromStr; 5 | use crate::state::{Result}; 6 | 7 | use solana_client::rpc_client::RpcClient; 8 | use solana_program::{pubkey::Pubkey}; 9 | use solana_sdk::{ 10 | signer::Signer, 11 | instruction::{AccountMeta, Instruction}, 12 | transaction::Transaction, signer::keypair::read_keypair_file, 13 | }; 14 | use borsh::{BorshDeserialize, BorshSerialize}; 15 | use solana_transaction_status::UiTransactionEncoding; 16 | 17 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 18 | pub enum SeedMethod { 19 | ShiftMurmur, 20 | SHA256Hash, 21 | None 22 | } 23 | 24 | fn get_method_from_int(index: u64) -> SeedMethod { 25 | if index == 0 { 26 | return SeedMethod::ShiftMurmur; 27 | } else if index == 1 { 28 | return SeedMethod::SHA256Hash; 29 | } 30 | else { 31 | return SeedMethod::None; 32 | } 33 | } 34 | 35 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 36 | pub struct SeedMeta { 37 | pub method : SeedMethod 38 | } 39 | 40 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 41 | pub enum RNGInstruction { 42 | 43 | GenerateSeed { 44 | metadata : SeedMeta 45 | } 46 | } 47 | const URL: &str = "https://api.devnet.solana.com"; 48 | 49 | 50 | fn main() { 51 | 52 | let args: Vec = env::args().collect(); 53 | let key_file = &args[1]; 54 | let function = &args[2]; 55 | 56 | if function == "generate_seed" { 57 | let index_arg = &args[3]; 58 | let index: u64 = index_arg.parse().unwrap(); 59 | let method = get_method_from_int(index); 60 | if let Err(err) = generate_seed(key_file, method) { 61 | eprintln!("{:?}", err); 62 | std::process::exit(1); 63 | } 64 | } 65 | 66 | 67 | } 68 | 69 | fn generate_seed(key_file: &String, method: SeedMethod) ->Result<()> { 70 | 71 | // (2) Create a new Keypair for the new account 72 | let wallet = read_keypair_file(key_file).unwrap(); 73 | 74 | // (3) Create RPC client to be used to talk to Solana cluster 75 | let connection = RpcClient::new(URL); 76 | 77 | let program = Pubkey::from_str("Hqw9GzaxEg1efH8BciNN5D32A5fMAfBdDM3qudRdb9o5").unwrap(); 78 | let btc_key = Pubkey::from_str("HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J").unwrap(); 79 | let eth_key = Pubkey::from_str("EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw").unwrap(); 80 | let sol_key = Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap(); 81 | 82 | 83 | let meta_data = SeedMeta{method: method}; 84 | 85 | let gen_seed_idx = Instruction::new_with_borsh( 86 | program, 87 | &RNGInstruction::GenerateSeed{metadata : meta_data}, 88 | vec![ 89 | AccountMeta::new_readonly(btc_key, false), 90 | AccountMeta::new_readonly(eth_key, false), 91 | AccountMeta::new_readonly(sol_key, false) 92 | ], 93 | ); 94 | 95 | // (7) Build transaction wrapping the create account instruction signed by both accounts 96 | let signers = [&wallet]; 97 | let instructions = vec![gen_seed_idx]; 98 | let recent_hash = connection.get_latest_blockhash()?; 99 | 100 | let txn = Transaction::new_signed_with_payer( 101 | &instructions, 102 | Some(&wallet.pubkey()), 103 | &signers, 104 | recent_hash, 105 | ); 106 | 107 | // (8) Send transaction to the cluster and wait for confirmation 108 | let signature = connection.send_and_confirm_transaction(&txn)?; 109 | println!("signature: {}", signature); 110 | let response = connection.get_transaction(&signature, UiTransactionEncoding::Json)?; 111 | println!("result: {:#?}", response); 112 | 113 | 114 | Ok(println!("Success!")) 115 | } 116 | -------------------------------------------------------------------------------- /Pyth/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | 4 | #[derive(Error, Debug)] 5 | pub enum Error { 6 | #[error("failed to read solana config file: ({0})")] 7 | ConfigReadError(std::io::Error), 8 | 9 | #[error("invalid config: ({0})")] 10 | InvalidConfig(String), 11 | 12 | #[error("serialization error: ({0})")] 13 | SerializationError(std::io::Error), 14 | 15 | #[error("solana client error: ({0})")] 16 | ClientError(#[from] solana_client::client_error::ClientError), 17 | 18 | #[error("error in public key derivation: ({0})")] 19 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 20 | } 21 | 22 | pub type Result = std::result::Result; -------------------------------------------------------------------------------- /Pyth/program/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /Pyth/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_pyth_v1" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.10.21" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.2.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "0.9.3" 14 | murmur3 = "0.5.1" 15 | pyth-sdk-solana = "0.4.2" 16 | sha2 = "0.10.2" 17 | 18 | [lib] 19 | crate-type = ["cdylib", "lib"] 20 | -------------------------------------------------------------------------------- /Pyth/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /Pyth/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /Pyth/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | // entrypoint has only one allowed instruction: GenerateRandom 8 | // this will generate 512 random u64's given the method specified in the 9 | // 'method' argument for that instruction (see instruction.rs for more detail) 10 | entrypoint!(process_instruction); 11 | fn process_instruction( 12 | program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | Processor::process(program_id, accounts, instruction_data) 17 | } -------------------------------------------------------------------------------- /Pyth/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum RNGError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | } 11 | 12 | impl From for ProgramError { 13 | fn from(e: RNGError) -> Self { 14 | ProgramError::Custom(e as u32) 15 | } 16 | } -------------------------------------------------------------------------------- /Pyth/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::error::RNGError::InvalidInstruction; 5 | use crate::state::{SeedMeta}; 6 | 7 | 8 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 9 | pub enum SeedInstruction { 10 | 11 | GenerateSeed{ 12 | metadata: SeedMeta 13 | } 14 | } 15 | 16 | impl SeedInstruction { 17 | /// Unpacks a byte buffer into a [EscrowInstruction]. 18 | pub fn unpack(input: &[u8]) -> Result { 19 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 20 | Ok(match tag { 21 | 0 => Self::GenerateSeed{ 22 | metadata: SeedMeta::try_from_slice(&rest)?, 23 | }, 24 | _ => return Err(InvalidInstruction.into()), 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pyth/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; -------------------------------------------------------------------------------- /Pyth/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | pub struct SeedStruct { 4 | pub seed_prices : [u64; 9] 5 | } 6 | 7 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 8 | pub enum SeedMethod { 9 | ShiftMurmur, 10 | SHA256Hash, 11 | None 12 | } 13 | 14 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 15 | pub struct SeedMeta { 16 | pub method : SeedMethod 17 | } -------------------------------------------------------------------------------- /Pyth/python/random_entropy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | import matplotlib.pyplot as plt 4 | 5 | def get_entropy(randoms, n_bins = 50): 6 | hist1 = np.histogram(randoms, bins=n_bins, range=(0,1), density=True) 7 | ent = 0 8 | for i in hist1[0]: 9 | if(i == 0): 10 | continue 11 | 12 | ent -= i * math.log(abs(i)) 13 | return hist1, np.exp(ent) 14 | 15 | 16 | n_bins = 10 17 | d_values = np.loadtxt("seed_values") 18 | d_diffs = np.abs(d_values[1:] - d_values[:-1]) 19 | hist, actual = get_entropy(d_values, n_bins) 20 | hist, actual_diffs = get_entropy(d_diffs, n_bins) 21 | 22 | e = [] 23 | e_diffs = [] 24 | for i in range(1000): 25 | r = np.random.uniform(0, 1, len(d_values)) 26 | h, ent = get_entropy(r, n_bins) 27 | e.append(ent) 28 | r_diffs = np.abs(r[1:] - r[:-1]) 29 | h, ent = get_entropy(r_diffs, n_bins) 30 | e_diffs.append(ent) 31 | 32 | 33 | plt.rcParams["figure.figsize"] = (11,10) 34 | plt.hist(e) 35 | plt.axvline(actual, lw=4, color="black") 36 | ax = plt.gca() 37 | ax.axes.yaxis.set_visible(False) 38 | plt.xticks([0.97, 0.98, 0.99, 1], fontsize=20) 39 | plt.show() 40 | 41 | plt.rcParams["figure.figsize"] = (11,10) 42 | plt.hist(e_diffs) 43 | plt.axvline(actual_diffs, lw=4, color="black") 44 | ax = plt.gca() 45 | ax.axes.yaxis.set_visible(False) 46 | plt.xticks([0.11, 0.13, 0.15, 0.17, 0.19], fontsize=20) 47 | plt.show() 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo holds the source code for the examples we describe on our blog at daoplays.org. 2 | 3 | random_numbers: 4 | https://www.daoplays.org/blog/random_numbers 5 | 6 | Pyth: 7 | https://www.daoplays.org/blog/pyth_seeds 8 | 9 | charity_token_launch: 10 | https://www.daoplays.org/blog/charity_token_launch 11 | 12 | solana_streamer: 13 | https://www.daoplays.org/blog/solana_streamer 14 | 15 | charity_auction 16 | https://www.daoplays.org/blog/charity_auction 17 | 18 | token_2022 19 | https://www.daoplays.org/blog/intro_token_2022 20 | 21 | transfer_hook 22 | https://www.daoplays.org/blog/transfer_hook 23 | 24 | metaplex core 25 | https://www.daoplays.org/blog/core_p1 26 | 27 | options 28 | coming soon! 29 | -------------------------------------------------------------------------------- /charity_auction/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /charity_auction/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /charity_auction/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" 16 | rand = "0.8.5" 17 | spl-associated-token-account = "1.0.5" 18 | enum-map = "1.1.1" 19 | 20 | -------------------------------------------------------------------------------- /charity_auction/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use enum_map::{enum_map, Enum}; 4 | use solana_program::{pubkey::Pubkey}; 5 | 6 | pub const MAX_BIDDERS : usize = 1024; 7 | pub const MAX_WINNERS : usize = 4; 8 | pub const TOKENS_WON : u64 = 1; 9 | pub const BID_BLOCK : usize = 128; 10 | pub const KEY_BLOCK : usize = 16; 11 | pub const N_BID_BLOCKS : usize = 8; 12 | pub const N_KEY_BLOCKS : usize = 64; 13 | 14 | 15 | 16 | #[derive(Error, Debug)] 17 | pub enum Error { 18 | #[error("failed to read solana config file: ({0})")] 19 | ConfigReadError(std::io::Error), 20 | 21 | #[error("invalid config: ({0})")] 22 | InvalidConfig(String), 23 | 24 | #[error("serialization error: ({0})")] 25 | SerializationError(std::io::Error), 26 | 27 | #[error("solana client error: ({0})")] 28 | ClientError(#[from] solana_client::client_error::ClientError), 29 | 30 | #[error("error in public key derivation: ({0})")] 31 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 32 | } 33 | 34 | pub type Result = std::result::Result; 35 | 36 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 37 | pub struct InitMeta { 38 | // the amount of DPTTs to be sent to the program 39 | pub amount : u64 40 | } 41 | 42 | // enum that lists the supported charities for this token launch 43 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Enum, Copy)] 44 | pub enum Charity { 45 | 46 | UkraineERF, 47 | WaterOrg, 48 | OneTreePlanted, 49 | EvidenceAction, 50 | GirlsWhoCode, 51 | OutrightActionInt, 52 | TheLifeYouCanSave, 53 | InvalidCharity 54 | 55 | } 56 | 57 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 58 | pub struct BidData { 59 | // the amount in lamports that will be donated to charity 60 | pub amount_charity : u64, 61 | // the amount in lamports being paid to the developers 62 | pub amount_dao : u64, 63 | // the chosen charity 64 | pub charity : Charity 65 | } 66 | 67 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 68 | pub enum AuctionInstruction { 69 | 70 | CreateDataAccount { 71 | metadata : InitMeta 72 | }, 73 | 74 | PlaceBid { 75 | // the price to bid in lamports 76 | bid_data: BidData 77 | }, 78 | 79 | SelectWinners, 80 | 81 | SendTokens 82 | } 83 | 84 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 85 | pub struct CharityData { 86 | // the total donated to each charity 87 | pub charity_totals : [u64 ; 7], 88 | // the total donated overall 89 | pub donated_total : u64, 90 | // the total paid overall 91 | pub paid_total : u64, 92 | // the number of participating accounts 93 | pub n_donations : u64 94 | } 95 | 96 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 97 | pub struct State { 98 | 99 | // this is the last time we actually chose winners, and decides how soon in the future will we choose again 100 | pub prev_choose_winners_time: i64, 101 | 102 | // the number of active bids in the system up to MAX_BIDDERS 103 | pub n_bidders: u16, 104 | // the sum of all the current bids 105 | pub total_bid_amount : u64, 106 | 107 | // for each bid we track the key, amount and time 108 | pub bid_keys : [Pubkey; MAX_BIDDERS], 109 | pub bid_amounts: [u64; MAX_BIDDERS], 110 | pub bid_times: [i64; MAX_BIDDERS], 111 | 112 | // the number of winners to be chosen, up to MAX_WINNERS 113 | pub n_winners : u8, 114 | pub winners: [Pubkey; MAX_WINNERS], 115 | 116 | // summary of the charity stats for the auction 117 | pub charity_data : CharityData 118 | } -------------------------------------------------------------------------------- /charity_auction/client/src/utils.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoplays/solana_examples/16c8cf076a68fcaa1f0571520483d7b57b22f1ff/charity_auction/client/src/utils.rs -------------------------------------------------------------------------------- /charity_auction/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | -------------------------------------------------------------------------------- /charity_auction/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "charity_lottery_v06" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "1.11.1" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.3.0", features = ["no-entrypoint"]} 12 | spl-associated-token-account = {version = "1.0.5", features = ["no-entrypoint"]} 13 | 14 | arrayref = "0.3.6" 15 | borsh = "0.9.3" 16 | enum-map = "1.1.1" 17 | murmur3 = "0.5.1" 18 | pyth-sdk-solana = "0.4.2" 19 | 20 | [lib] 21 | crate-type = ["cdylib", "lib"] 22 | -------------------------------------------------------------------------------- /charity_auction/program/LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /charity_auction/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /charity_auction/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /charity_auction/program/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use spl_associated_token_account::get_associated_token_address; 2 | use solana_program::{pubkey::Pubkey, declare_id}; 3 | // functions to calculate expected public keys 4 | 5 | mod btc_oracle { 6 | use super::*; 7 | declare_id!("HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J"); 8 | } 9 | mod eth_oracle { 10 | use super::*; 11 | declare_id!("EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"); 12 | } 13 | mod sol_oracle { 14 | use super::*; 15 | declare_id!("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix"); 16 | } 17 | // This can also be replaced with pubkey ("CU8AequXiVdXyVKc7Vqg2jiBDJgPwapMbcBrm7EVnTtm") if you are on a recent sdk 18 | mod daoplays { 19 | use super::*; 20 | declare_id!("2BLkynLAWGwW58SLDAnhwsoiAuVtzqyfHKA3W3MJFwEF"); 21 | } 22 | 23 | mod token_mint { 24 | use super::*; 25 | declare_id!("CisHceikLeKxYiUqgDVduw2py2GEK71FTRykXGdwf22h"); 26 | } 27 | 28 | pub fn get_expected_btc_key() -> Pubkey 29 | { 30 | btc_oracle::ID 31 | } 32 | 33 | pub fn get_expected_eth_key() -> Pubkey 34 | { 35 | eth_oracle::ID 36 | } 37 | 38 | pub fn get_expected_sol_key() -> Pubkey 39 | { 40 | sol_oracle::ID 41 | } 42 | 43 | pub fn get_expected_daoplays_key() -> Pubkey 44 | { 45 | daoplays::ID 46 | } 47 | 48 | pub fn get_expected_token_mint_key() -> Pubkey 49 | { 50 | token_mint::ID 51 | } 52 | 53 | pub fn get_expected_daoplays_token_key() -> Pubkey 54 | { 55 | get_associated_token_address( 56 | &get_expected_daoplays_key(), 57 | &get_expected_token_mint_key() 58 | ) 59 | } 60 | 61 | pub fn get_pda_bump() -> u8 62 | { 63 | 253 64 | } 65 | 66 | pub fn get_expected_program_address_key(program_id : &Pubkey) -> (Pubkey, u8) 67 | { 68 | let program_address = Pubkey::create_program_address(&[b"token_account", &[get_pda_bump()]], &program_id).unwrap(); 69 | 70 | (program_address, get_pda_bump()) 71 | } 72 | 73 | pub fn get_expected_data_account_key(program_id : &Pubkey) -> Pubkey 74 | { 75 | let data_key = Pubkey::create_with_seed( 76 | &get_expected_daoplays_key(), 77 | "data_account", 78 | &program_id, 79 | ).unwrap(); 80 | 81 | return data_key; 82 | 83 | } 84 | 85 | pub fn get_expected_program_token_key(program_id : &Pubkey) -> Pubkey 86 | { 87 | get_associated_token_address( 88 | &get_expected_program_address_key(program_id).0, 89 | &get_expected_token_mint_key() 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /charity_auction/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | 8 | entrypoint!(process_instruction); 9 | fn process_instruction( 10 | program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | instruction_data: &[u8], 13 | ) -> ProgramResult { 14 | 15 | Processor::process(program_id, accounts, instruction_data) 16 | } -------------------------------------------------------------------------------- /charity_auction/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum DaoPlaysError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | 11 | #[error("Invalid bid amount for button press")] 12 | InvalidButtonBid 13 | } 14 | 15 | impl From for ProgramError { 16 | fn from(e: DaoPlaysError) -> Self { 17 | ProgramError::Custom(e as u32) 18 | } 19 | } -------------------------------------------------------------------------------- /charity_auction/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use crate::state::Charity; 4 | use crate::error::DaoPlaysError::InvalidInstruction; 5 | 6 | 7 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 8 | pub struct BidData { 9 | // the amount in lamports that will be donated to charity 10 | pub amount_charity : u64, 11 | // the amount in lamports being paid to the developers 12 | pub amount_dao : u64, 13 | // the chosen charity 14 | pub charity : Charity 15 | } 16 | 17 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 18 | pub struct InitMeta { 19 | // the amount of DPTTs to be sent to the program 20 | pub amount : u64 21 | } 22 | 23 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 24 | pub enum DaoPlaysInstruction { 25 | 26 | CreateDataAccount { 27 | metadata : InitMeta 28 | }, 29 | 30 | PlaceBid { 31 | // the price to bid in lamports 32 | bid_data: BidData 33 | }, 34 | 35 | SelectWinners, 36 | 37 | SendTokens 38 | } 39 | 40 | impl DaoPlaysInstruction { 41 | /// Unpacks a byte buffer into a [EscrowInstruction]. 42 | pub fn unpack(input: &[u8]) -> Result { 43 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 44 | Ok(match tag { 45 | 0 => Self::CreateDataAccount { 46 | metadata: InitMeta::try_from_slice(&rest)?, 47 | }, 48 | 1 => Self::PlaceBid{ 49 | bid_data: BidData::try_from_slice(&rest)?, 50 | }, 51 | 2 => Self::SelectWinners, 52 | 3 => Self::SendTokens, 53 | _ => return Err(InvalidInstruction.into()), 54 | }) 55 | } 56 | } -------------------------------------------------------------------------------- /charity_auction/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; 6 | pub mod accounts; 7 | pub mod utils; 8 | pub mod randoms; -------------------------------------------------------------------------------- /charity_auction/program/src/randoms.rs: -------------------------------------------------------------------------------- 1 | use crate::state::SeedStruct; 2 | use std::mem; 3 | use solana_program::{ 4 | account_info::AccountInfo, 5 | msg 6 | }; 7 | use pyth_sdk_solana::{load_price_feed_from_account_info}; 8 | use murmur3::murmur3_x64_128; 9 | 10 | 11 | 12 | // A xorshift* generator as suggested by Marsaglia. 13 | // The following 64-bit generator with 64 bits of state has a maximal period of 2^64−1 14 | // and fails only the MatrixRank test of BigCrush 15 | // see https://en.wikipedia.org/wiki/Xorshift 16 | pub fn shift_seed(mut seed: u64) -> u64 { 17 | seed ^= seed >> 12; 18 | seed ^= seed << 25; 19 | seed ^= seed >> 27; 20 | seed *= 0x2545F4914F6CDD1D; 21 | 22 | return seed; 23 | 24 | } 25 | 26 | pub fn generate_random(seed: u64) -> f64 { 27 | 28 | let tmp = 0x3FF0000000000000 | (seed & 0xFFFFFFFFFFFFF); 29 | let result: f64 = unsafe { mem::transmute(tmp) }; 30 | 31 | 32 | return result - 1.0; 33 | } 34 | 35 | 36 | unsafe fn any_as_u8_slice(p: &T) -> &[u8] { 37 | ::std::slice::from_raw_parts( 38 | (p as *const T) as *const u8, 39 | ::std::mem::size_of::(), 40 | ) 41 | } 42 | 43 | pub fn generate_seed<'a>( 44 | btc_account_info : &AccountInfo<'a>, 45 | eth_account_info : &AccountInfo<'a>, 46 | sol_account_info : &AccountInfo<'a>, 47 | ) ->u64 { 48 | 49 | let btc_price_feed = load_price_feed_from_account_info( &btc_account_info ).unwrap(); 50 | let eth_price_feed = load_price_feed_from_account_info( ð_account_info ).unwrap(); 51 | let sol_price_feed = load_price_feed_from_account_info( &sol_account_info ).unwrap(); 52 | 53 | let btc_price_struct = btc_price_feed.get_current_price().unwrap(); 54 | let eth_price_struct = eth_price_feed.get_current_price().unwrap(); 55 | let sol_price_struct = sol_price_feed.get_current_price().unwrap(); 56 | 57 | let btc_price_value = u64::try_from(btc_price_struct.price).unwrap(); 58 | let btc_price_error = btc_price_struct.conf; 59 | 60 | let eth_price_value = u64::try_from(eth_price_struct.price).unwrap(); 61 | let eth_price_error = eth_price_struct.conf; 62 | 63 | let sol_price_value = u64::try_from(sol_price_struct.price).unwrap(); 64 | let sol_price_error = sol_price_struct.conf; 65 | 66 | msg!("btc price: ({} +/- {}) x 10^{}", btc_price_value, btc_price_error, btc_price_struct.expo); 67 | msg!("eth price: ({} +/- {}) x 10^{}", eth_price_value, eth_price_error, eth_price_struct.expo); 68 | msg!("sol price: ({} +/- {}) x 10^{}", sol_price_value, sol_price_error, sol_price_struct.expo); 69 | 70 | let mut seed_values = SeedStruct { seed_prices : [0; 9] }; 71 | 72 | 73 | //msg!("Generating seed"); 74 | 75 | seed_values.seed_prices[0] = shift_seed(shift_seed(btc_price_value + btc_price_error)); 76 | seed_values.seed_prices[1] = shift_seed(shift_seed(btc_price_value)); 77 | seed_values.seed_prices[2] = shift_seed(shift_seed(btc_price_value - btc_price_error)); 78 | 79 | seed_values.seed_prices[3] = shift_seed(shift_seed(eth_price_value + eth_price_error)); 80 | seed_values.seed_prices[4] = shift_seed(shift_seed(eth_price_value)); 81 | seed_values.seed_prices[5] = shift_seed(shift_seed(eth_price_value - eth_price_error)); 82 | 83 | seed_values.seed_prices[6] = shift_seed(shift_seed(sol_price_value + sol_price_error)); 84 | seed_values.seed_prices[7] = shift_seed(shift_seed(sol_price_value)); 85 | seed_values.seed_prices[8] = shift_seed(shift_seed(sol_price_value - sol_price_error)); 86 | 87 | let mut vec_to_hash = unsafe{any_as_u8_slice(&seed_values)}; 88 | let h = murmur3_x64_128(&mut vec_to_hash, 0).unwrap(); 89 | 90 | // we can take our 128bit number and get two 64bit values 91 | let lower = u64::try_from(h & 0xFFFFFFFFFFFFFFFF).unwrap(); 92 | let upper = u64::try_from((h >> 64) & 0xFFFFFFFFFFFFFFFF).unwrap(); 93 | 94 | let seed = lower ^ upper; 95 | 96 | 97 | return seed; 98 | } -------------------------------------------------------------------------------- /charity_auction/program/tests/integration.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "test-bpf")] 2 | 3 | use { 4 | assert_matches::*, 5 | solana_program::{ 6 | instruction::{AccountMeta, Instruction}, 7 | pubkey::Pubkey, 8 | }, 9 | solana_sdk::{signature::Signer, transaction::Transaction}, 10 | solana_validator::test_validator::*, 11 | }; 12 | 13 | #[test] 14 | fn test_validator_transaction() { 15 | solana_logger::setup_with_default("solana_program_runtime=debug"); 16 | let program_id = Pubkey::new_unique(); 17 | 18 | let (test_validator, payer) = TestValidatorGenesis::default() 19 | .add_program("bpf_program_template", program_id) 20 | .start(); 21 | let rpc_client = test_validator.get_rpc_client(); 22 | 23 | let blockhash = rpc_client.get_latest_blockhash().unwrap(); 24 | 25 | let mut transaction = Transaction::new_with_payer( 26 | &[Instruction { 27 | program_id, 28 | accounts: vec![AccountMeta::new(payer.pubkey(), false)], 29 | data: vec![1, 2, 3], 30 | }], 31 | Some(&payer.pubkey()), 32 | ); 33 | transaction.sign(&[&payer], blockhash); 34 | 35 | assert_matches!(rpc_client.send_and_confirm_transaction(&transaction), Ok(_)); 36 | } 37 | -------------------------------------------------------------------------------- /charity_token_launch/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /charity_token_launch/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" 16 | rand = "0.8.5" 17 | spl-associated-token-account = "1.0.5" 18 | enum-map = "1.1.1" -------------------------------------------------------------------------------- /charity_token_launch/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use thiserror::Error; 3 | 4 | 5 | 6 | #[derive(Error, Debug)] 7 | pub enum Error { 8 | #[error("failed to read solana config file: ({0})")] 9 | ConfigReadError(std::io::Error), 10 | 11 | #[error("invalid config: ({0})")] 12 | InvalidConfig(String), 13 | 14 | #[error("serialization error: ({0})")] 15 | SerializationError(std::io::Error), 16 | 17 | #[error("solana client error: ({0})")] 18 | ClientError(#[from] solana_client::client_error::ClientError), 19 | 20 | #[error("error in public key derivation: ({0})")] 21 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 22 | } 23 | 24 | pub type Result = std::result::Result; 25 | 26 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 27 | pub struct ICOData { 28 | pub charity_totals : [u64 ; 7], 29 | pub donated_total : u64, 30 | pub paid_total : u64, 31 | pub n_donations : u64 32 | } 33 | -------------------------------------------------------------------------------- /charity_token_launch/client/src/utils.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshSerialize}; 2 | use crate::state::{ICOData, Error, Result}; 3 | 4 | /// Determines and reports the size of greeting data. 5 | pub fn get_state_size() -> usize { 6 | let encoded = ICOData {charity_totals: [0; 7], donated_total : 0, paid_total : 0, n_donations : 0} 7 | .try_to_vec().unwrap(); 8 | 9 | encoded.len() 10 | } 11 | -------------------------------------------------------------------------------- /charity_token_launch/program/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /charity_token_launch/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "charity_ico_v2" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "1.11.1" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.3.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "0.9.3" 14 | spl-associated-token-account = {version = "1.0.5", features = ["no-entrypoint"]} 15 | enum-map = "1.1.1" 16 | 17 | [lib] 18 | crate-type = ["cdylib", "lib"] 19 | -------------------------------------------------------------------------------- /charity_token_launch/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /charity_token_launch/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /charity_token_launch/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | // entrypoint has only one allowed instruction: GenerateRandom 8 | // this will generate 512 random u64's given the method specified in the 9 | // 'method' argument for that instruction (see instruction.rs for more detail) 10 | entrypoint!(process_instruction); 11 | fn process_instruction( 12 | program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | Processor::process(program_id, accounts, instruction_data) 17 | } -------------------------------------------------------------------------------- /charity_token_launch/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum RNGError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | } 11 | 12 | impl From for ProgramError { 13 | fn from(e: RNGError) -> Self { 14 | ProgramError::Custom(e as u32) 15 | } 16 | } -------------------------------------------------------------------------------- /charity_token_launch/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::error::RNGError::InvalidInstruction; 5 | use crate::state::{JoinMeta, InitMeta}; 6 | 7 | 8 | 9 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 10 | pub enum TokenLaunchInstruction { 11 | 12 | // Function that sets up the token launch and initialises program data and transfers tokens, expects 11 accounts: 13 | //funding_account_info 14 | //program_derived_account_info 15 | 16 | //token_source_account_info 17 | //program_token_account_info 18 | //token_mint_account_info 19 | 20 | //supporters_token_source_account_info 21 | //program_supporters_token_account_info 22 | //supporters_token_mint_account_info 23 | 24 | //token_program_account_info 25 | //associated_token_account_info 26 | //system_program_account_info 27 | InitTokenLaunch { 28 | metadata: InitMeta 29 | }, 30 | 31 | 32 | // function that allows a user to participate in the token launch. Sends SOL to the charity and developers and tokens to the user 33 | // expects 13 accounts to be passed to the function: 34 | //joiner_account_info 35 | //joiner_token_account_info 36 | //joiner_supporters_token_account_info 37 | 38 | //program_data_account_info 39 | //program_token_account_info 40 | //program_supporters_token_account_info 41 | 42 | //charity_account_info 43 | //daoplays_account_info 44 | 45 | //token_mint_account_info 46 | //supporters_token_mint_account_info 47 | 48 | //token_program_account_info 49 | //associated_token_account_info 50 | //system_program_account_info 51 | 52 | JoinTokenLaunch { 53 | metadata: JoinMeta 54 | }, 55 | 56 | // function to end the token launch and transfer remaining tokens away from the program 57 | // expects 10 accounts to be passed 58 | //daoplays_account_info 59 | //daoplays_token_account_info 60 | //daoplays_supporters_token_account_info 61 | 62 | //program_account_info 63 | //program_token_account_info 64 | //program_supporters_token_account_info 65 | 66 | //token_mint_account_info 67 | //supporters_token_mint_account_info 68 | 69 | //token_program_account_info 70 | //system_program_account_info 71 | 72 | EndTokenLaunch 73 | } 74 | 75 | impl TokenLaunchInstruction { 76 | /// Unpacks a byte buffer into a [EscrowInstruction]. 77 | pub fn unpack(input: &[u8]) -> Result { 78 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 79 | Ok(match tag { 80 | 81 | 0 => Self::InitTokenLaunch { 82 | metadata: InitMeta::try_from_slice(&rest)?, 83 | }, 84 | 1 => Self::JoinTokenLaunch { 85 | metadata: JoinMeta::try_from_slice(&rest)?, 86 | }, 87 | 2 => Self::EndTokenLaunch, 88 | _ => return Err(InvalidInstruction.into()), 89 | }) 90 | } 91 | } -------------------------------------------------------------------------------- /charity_token_launch/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; -------------------------------------------------------------------------------- /charity_token_launch/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use enum_map::{Enum}; 3 | 4 | // enum that lists the supported charities for this token launch 5 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Enum, Copy)] 6 | pub enum Charity { 7 | 8 | UkraineERF, 9 | WaterOrg, 10 | OneTreePlanted, 11 | EvidenceAction, 12 | GirlsWhoCode, 13 | OutrightActionInt, 14 | TheLifeYouCanSave 15 | 16 | } 17 | 18 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 19 | pub struct JoinMeta { 20 | // the amount in lamports that will be donated to charity 21 | pub amount_charity : u64, 22 | // the amount in lamports being paid to the developers 23 | pub amount_dao : u64, 24 | // the chosen charity 25 | pub charity : Charity 26 | } 27 | 28 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 29 | pub struct InitMeta { 30 | // the amount of DPTTs to be sent to the program 31 | pub amount : u64, 32 | // the amount of supporter tokens to be send to the program 33 | pub supporter_amount : u64 34 | } 35 | 36 | // on chain data that saves summary stats of the token launch 37 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 38 | pub struct TokenLaunchData { 39 | // the total donated to each charity 40 | pub charity_totals : [u64 ; 7], 41 | // the total donated overall 42 | pub donated_total : u64, 43 | // the total paid overall 44 | pub paid_total : u64, 45 | // the number of participating accounts 46 | pub n_donations : u64 47 | } 48 | 49 | // helper function to return the size of the TokenLaunchData so we can check the lamports required to be rent-exempt 50 | pub fn get_state_size() -> usize { 51 | let encoded = TokenLaunchData {charity_totals: [0; 7], donated_total : 0, paid_total : 0, n_donations : 0} 52 | .try_to_vec().unwrap(); 53 | 54 | encoded.len() 55 | } -------------------------------------------------------------------------------- /core/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /core/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | arrayref = "0.3.7" 10 | bytemuck = { version = "1.13.1", features = ["derive"] } 11 | num-derive = "0.4" 12 | num-traits = "0.2" 13 | num_enum = "0.6.1" 14 | spl-discriminator = "0.1.0" 15 | spl-type-length-value = "0.3.0" 16 | spl-tlv-account-resolution = "0.4.0" 17 | solana-sdk = "1.16.7" 18 | solana-client = "1.16.7" 19 | solana-program = "1.16.7" 20 | spl-token = {version = "3.5.0", features = ["no-entrypoint"]} 21 | spl-token-2022 = {version = "0.9.0", features = ["no-entrypoint"]} 22 | borsh = "0.10.3" 23 | solana-transaction-status = "1.16.0" 24 | rand = "0.7.0" 25 | spl-associated-token-account = "2.0" 26 | enum-map = "1.1.1" 27 | thiserror = "1.0" 28 | async-trait = "0.1" 29 | futures-util = "0.3" 30 | -------------------------------------------------------------------------------- /core/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | use spl_discriminator::{ArrayDiscriminator, SplDiscriminate}; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum Error { 8 | #[error("failed to read solana config file: ({0})")] 9 | ConfigReadError(std::io::Error), 10 | 11 | #[error("invalid config: ({0})")] 12 | InvalidConfig(String), 13 | 14 | #[error("serialization error: ({0})")] 15 | SerializationError(std::io::Error), 16 | 17 | #[error("solana client error: ({0})")] 18 | ClientError(#[from] solana_client::client_error::ClientError), 19 | 20 | #[error("error in public key derivation: ({0})")] 21 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 22 | } 23 | 24 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 25 | pub struct CreateMeta { 26 | pub name: String, 27 | pub symbol: String, 28 | pub uri: String, 29 | } 30 | 31 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 32 | pub enum TokenInstruction { 33 | CreateCollection, 34 | CreateToken, 35 | } 36 | -------------------------------------------------------------------------------- /core/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | mpl-core-main 5 | -------------------------------------------------------------------------------- /core/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "core" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.18.11" 10 | thiserror = "1.0.24" 11 | borsh = "1.4.0" 12 | mpl-core = "0.5.0" 13 | toml_edit = "^0.21" 14 | proc-macro-crate = "^3.1.0" 15 | [lib] 16 | crate-type = ["cdylib", "lib"] 17 | 18 | [patch.crates-io] 19 | mpl-core = { path = "./mpl-core-main/clients/rust/" } -------------------------------------------------------------------------------- /core/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /core/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /core/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,msg 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction<'a>( 9 | program_id: &Pubkey, 10 | accounts: &'a [AccountInfo<'a>], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | msg!("process"); 14 | Processor::process(program_id, accounts, instruction_data) 15 | } 16 | -------------------------------------------------------------------------------- /core/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::program_error::ProgramError; 3 | 4 | use crate::error::NewError::InvalidInstruction; 5 | 6 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 7 | pub struct CreateMeta { 8 | pub name: String, 9 | pub uri: String, 10 | } 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 13 | pub enum TokenInstruction { 14 | CreateCollection { args: CreateMeta }, 15 | CreateToken { args: CreateMeta }, 16 | TransferToken, 17 | } 18 | 19 | impl TokenInstruction { 20 | /// Unpacks a byte buffer into a [EscrowInstruction]. 21 | pub fn unpack(input: &[u8]) -> Result { 22 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 23 | Ok(match tag { 24 | 0 => Self::CreateCollection { 25 | args: CreateMeta::try_from_slice(&rest)?, 26 | }, 27 | 1 => Self::CreateToken { 28 | args: CreateMeta::try_from_slice(&rest)?, 29 | }, 30 | 2 => Self::TransferToken, 31 | _ => return Err(InvalidInstruction.into()), 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod utils; 6 | -------------------------------------------------------------------------------- /core/program/src/utils.rs: -------------------------------------------------------------------------------- 1 | use mpl_core::instructions::TransferV1CpiBuilder; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint::ProgramResult, 5 | instruction::AccountMeta, 6 | msg, 7 | program::invoke, 8 | program_error::ProgramError, 9 | pubkey::Pubkey, 10 | }; 11 | 12 | pub fn check_program_data_account<'a>( 13 | account_info: &'a AccountInfo<'a>, 14 | program_id: &Pubkey, 15 | seed: Vec<&[u8]>, 16 | name: String, 17 | ) -> Result { 18 | if seed.len() == 1 { 19 | let (expected_data_account, bump_seed) = 20 | Pubkey::find_program_address(&[seed[0]], &program_id); 21 | 22 | // the third account is the user's token account 23 | if account_info.key != &expected_data_account { 24 | msg!( 25 | "expected program data account {} {}", 26 | name, 27 | expected_data_account 28 | ); 29 | return Err(ProgramError::InvalidAccountData); 30 | } 31 | 32 | return Ok(bump_seed); 33 | } 34 | 35 | let (expected_data_account, bump_seed) = 36 | Pubkey::find_program_address(&[seed[0], seed[1]], &program_id); 37 | 38 | // the third account is the user's token account 39 | if account_info.key != &expected_data_account { 40 | msg!( 41 | "expected program data account {} {}", 42 | name, 43 | expected_data_account 44 | ); 45 | return Err(ProgramError::InvalidAccountData); 46 | } 47 | 48 | return Ok(bump_seed); 49 | } 50 | 51 | pub fn transfer<'a>( 52 | _program_id: &Pubkey, 53 | accounts: &'a [AccountInfo<'a>], 54 | ) -> Result<(), ProgramError> { 55 | let account_info_iter = &mut accounts.iter(); 56 | let source_account_info = next_account_info(account_info_iter)?; 57 | let destination_account_info = next_account_info(account_info_iter)?; 58 | let asset_account_info: &AccountInfo<'a> = next_account_info(account_info_iter)?; 59 | let collection_account_info: &AccountInfo<'a> = next_account_info(account_info_iter)?; 60 | let core_program_account_info = next_account_info(account_info_iter)?; 61 | let system_program_account_info = next_account_info(account_info_iter)?; 62 | 63 | let _transfer = TransferV1CpiBuilder::new(core_program_account_info) 64 | .asset(asset_account_info) 65 | .authority(Some(source_account_info)) 66 | .payer(source_account_info) 67 | .new_owner(destination_account_info) 68 | .collection(Some(collection_account_info)) 69 | .system_program(Some(system_program_account_info)) 70 | .invoke() 71 | .unwrap(); 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /ice_cream/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /ice_cream/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" 16 | rand = "0.8.5" 17 | spl-associated-token-account = "1.0.5" 18 | enum-map = "1.1.1" -------------------------------------------------------------------------------- /ice_cream/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use thiserror::Error; 3 | use solana_program::{pubkey::Pubkey}; 4 | 5 | 6 | 7 | #[derive(Error, Debug)] 8 | pub enum Error { 9 | #[error("failed to read solana config file: ({0})")] 10 | ConfigReadError(std::io::Error), 11 | 12 | #[error("invalid config: ({0})")] 13 | InvalidConfig(String), 14 | 15 | #[error("serialization error: ({0})")] 16 | SerializationError(std::io::Error), 17 | 18 | #[error("solana client error: ({0})")] 19 | ClientError(#[from] solana_client::client_error::ClientError), 20 | 21 | #[error("error in public key derivation: ({0})")] 22 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 23 | } 24 | 25 | pub type Result = std::result::Result; 26 | 27 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 28 | pub struct CreateMeta { 29 | pub team_name : String 30 | } 31 | 32 | 33 | 34 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 35 | pub enum IceCreamInstruction { 36 | 37 | InitProgram, 38 | 39 | CreateTeam { 40 | metadata: CreateMeta 41 | }, 42 | 43 | CreateTeamLookup { 44 | metadata: CreateMeta 45 | }, 46 | 47 | Eat { 48 | metadata: CreateMeta 49 | } 50 | } 51 | 52 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 53 | pub struct ScoreMeta { 54 | // the total number of teams registered 55 | pub num_teams : u64, 56 | // the indices of the top ten teams 57 | pub top_ten_teams : [u64; 10], 58 | // the scores of the top ten teams 59 | pub top_ten_scores : [u64; 10], 60 | } 61 | 62 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 63 | pub struct TeamAccountMeta { 64 | // the total number of teams registered 65 | pub team_account : Pubkey 66 | 67 | } 68 | 69 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 70 | pub struct TeamMeta { 71 | pub team_name : [u8 ; 256], 72 | pub name_len : u64, 73 | // the mint address of this team 74 | pub mint_address : Pubkey, 75 | // the teams score 76 | pub score : u64, 77 | // the teams index 78 | pub index : u64 79 | } 80 | -------------------------------------------------------------------------------- /ice_cream/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | -------------------------------------------------------------------------------- /ice_cream/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ice_cream" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "1.11.1" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.3.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "0.9.3" 14 | spl-associated-token-account = {version = "1.0.5", features = ["no-entrypoint"]} 15 | enum-map = "1.1.1" 16 | 17 | [lib] 18 | crate-type = ["cdylib", "lib"] 19 | -------------------------------------------------------------------------------- /ice_cream/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /ice_cream/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /ice_cream/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction( 9 | program_id: &Pubkey, 10 | accounts: &[AccountInfo], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | Processor::process(program_id, accounts, instruction_data) 14 | } -------------------------------------------------------------------------------- /ice_cream/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum NewError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | #[error("Team Account Not Created")] 11 | TeamAccountNotCreated, 12 | #[error("User Doesn't own Team Token")] 13 | NoTeamTokens, 14 | #[error("Team Name too long")] 15 | InvalidTeamName, 16 | #[error("Invalid Token Mint")] 17 | InvalidTokenMint, 18 | #[error("Team Already Exists")] 19 | TeamAlreadyExists, 20 | } 21 | 22 | impl From for ProgramError { 23 | fn from(e: NewError) -> Self { 24 | ProgramError::Custom(e as u32) 25 | } 26 | } -------------------------------------------------------------------------------- /ice_cream/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::error::NewError::InvalidInstruction; 5 | use crate::state::{CreateMeta}; 6 | 7 | 8 | 9 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 10 | pub enum IceCreamInstruction { 11 | 12 | InitProgram, 13 | 14 | CreateTeam { 15 | metadata: CreateMeta 16 | }, 17 | 18 | CreateTeamLookup { 19 | metadata: CreateMeta 20 | }, 21 | 22 | Eat { 23 | metadata: CreateMeta 24 | } 25 | } 26 | 27 | impl IceCreamInstruction { 28 | /// Unpacks a byte buffer into a [EscrowInstruction]. 29 | pub fn unpack(input: &[u8]) -> Result { 30 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 31 | Ok(match tag { 32 | 33 | 0 => Self::InitProgram, 34 | 1 => Self::CreateTeam { 35 | metadata: CreateMeta::try_from_slice(&rest)? 36 | }, 37 | 2 => Self::CreateTeamLookup { 38 | metadata: CreateMeta::try_from_slice(&rest)? 39 | }, 40 | 3 => Self::Eat { 41 | metadata: CreateMeta::try_from_slice(&rest)? 42 | }, 43 | _ => return Err(InvalidInstruction.into()), 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /ice_cream/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; -------------------------------------------------------------------------------- /ice_cream/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | 4 | 5 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 6 | pub struct CreateMeta { 7 | pub team_name : String 8 | } 9 | 10 | 11 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 12 | pub struct ScoreMeta { 13 | // the total number of teams registered 14 | pub num_teams : u64, 15 | // the indices of the top ten teams 16 | pub top_ten_teams : [u64; 10], 17 | // the scores of the top ten teams 18 | pub top_ten_scores : [u64; 10], 19 | } 20 | 21 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 22 | pub struct TeamAccountMeta { 23 | // the total number of teams registered 24 | pub team_account : Pubkey 25 | 26 | } 27 | 28 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 29 | pub struct TeamMeta { 30 | pub team_name : [u8 ; 256], 31 | pub name_len : u64, 32 | // the mint address of this team 33 | pub mint_address : Pubkey, 34 | // the teams score 35 | pub score : u64, 36 | // the teams index 37 | pub index : u64 38 | } 39 | 40 | pub fn get_score_meta_size() -> usize { 41 | let encoded = ScoreMeta {num_teams: 0, top_ten_teams : [0; 10], top_ten_scores : [0; 10]} 42 | .try_to_vec().unwrap(); 43 | 44 | encoded.len() 45 | } 46 | 47 | /// Determines and reports the size of greeting data. 48 | pub fn get_team_lookup_meta_size() -> usize { 49 | let encoded = TeamAccountMeta {team_account : solana_program::system_program::id() } 50 | .try_to_vec().unwrap(); 51 | 52 | encoded.len() 53 | } 54 | 55 | /// Determines and reports the size of greeting data. 56 | pub fn get_team_meta_size() -> usize { 57 | let encoded = TeamMeta {team_name : [0; 256], name_len : 0, mint_address : solana_program::system_program::id(), score : 0, index : 0} 58 | .try_to_vec().unwrap(); 59 | 60 | encoded.len() 61 | } -------------------------------------------------------------------------------- /options/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /options/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | arrayref = "0.3.7" 10 | bytemuck = { version = "1.13.1", features = ["derive"] } 11 | num-derive = "0.4" 12 | num-traits = "0.2" 13 | num_enum = "0.6.1" 14 | spl-discriminator = "0.1.0" 15 | spl-type-length-value = "0.3.0" 16 | spl-tlv-account-resolution = "0.4.0" 17 | solana-sdk = "1.16.7" 18 | solana-client = "1.16.7" 19 | solana-program = "1.16.7" 20 | spl-token = {version = "3.5.0", features = ["no-entrypoint"]} 21 | spl-token-2022 = {version = "0.9.0", features = ["no-entrypoint"]} 22 | borsh = "0.10.3" 23 | solana-transaction-status = "1.16.0" 24 | rand = "0.7.0" 25 | spl-associated-token-account = "2.0" 26 | enum-map = "1.1.1" 27 | thiserror = "1.0" 28 | async-trait = "0.1" 29 | futures-util = "0.3" 30 | -------------------------------------------------------------------------------- /options/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | use spl_discriminator::{ArrayDiscriminator, SplDiscriminate}; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum Error { 8 | #[error("failed to read solana config file: ({0})")] 9 | ConfigReadError(std::io::Error), 10 | 11 | #[error("invalid config: ({0})")] 12 | InvalidConfig(String), 13 | 14 | #[error("serialization error: ({0})")] 15 | SerializationError(std::io::Error), 16 | 17 | #[error("solana client error: ({0})")] 18 | ClientError(#[from] solana_client::client_error::ClientError), 19 | 20 | #[error("error in public key derivation: ({0})")] 21 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 22 | } 23 | 24 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 25 | pub struct CreateMeta { 26 | pub name: String, 27 | pub uri: String, 28 | } 29 | 30 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 31 | pub enum TokenInstruction { 32 | Init, 33 | CreateCollection { args: CreateMeta }, 34 | CreateToken { args: CreateMeta }, 35 | } 36 | -------------------------------------------------------------------------------- /options/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | mpl-core-main 5 | -------------------------------------------------------------------------------- /options/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "options" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.18.11" 10 | thiserror = "1.0.24" 11 | borsh = "1.4.0" 12 | mpl-core = "0.5.0" 13 | toml_edit = "^0.21" 14 | proc-macro-crate = "^3.1.0" 15 | shank = "0.4.2" 16 | spl-token = {version = "4.0.0", features = ["no-entrypoint"]} 17 | spl-discriminator = "0.2.2" 18 | spl-type-length-value = "0.4.3" 19 | spl-tlv-account-resolution = "0.6.3" 20 | spl-pod = "0.2.2" 21 | spl-token-2022 = {version = "3.0.2", features = ["no-entrypoint"]} 22 | spl-associated-token-account = {version = "3.0.2", features = ["no-entrypoint"]} 23 | 24 | [lib] 25 | crate-type = ["cdylib", "lib"] 26 | 27 | [patch.crates-io] 28 | mpl-core = { path = "./mpl-core-main/clients/rust/" } 29 | -------------------------------------------------------------------------------- /options/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /options/program/configs/kinobi.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const k = require("@metaplex-foundation/kinobi"); 3 | 4 | // Paths. 5 | const clientDir = path.join(__dirname, "..", "clients"); 6 | const idlDir = path.join(__dirname, "..", "idls"); 7 | 8 | // Instanciate Kinobi. 9 | const kinobi = k.createFromIdls([path.join(idlDir, "mpl_core.json")]); 10 | 11 | // Update programs. 12 | kinobi.update( 13 | k.updateProgramsVisitor({ 14 | mplCoreProgram: { name: "mplCore" }, 15 | }) 16 | ); 17 | 18 | 19 | kinobi.update( 20 | new k.updateAccountsVisitor({ 21 | assetV1: { 22 | name: "baseAssetV1", 23 | }, 24 | collectionV1: { 25 | name: "baseCollectionV1", 26 | } 27 | }) 28 | ); 29 | 30 | kinobi.update(new k.updateDefinedTypesVisitor({ 31 | authority: { 32 | name: "pluginAuthority" 33 | } 34 | })) 35 | 36 | // Update instructions with default values 37 | kinobi.update( 38 | k.updateInstructionsVisitor({ 39 | // create: { 40 | // bytesCreatedOnChain: k.bytesFromAccount("assetAccount"), 41 | // }, 42 | transferV1: { 43 | arguments: { 44 | compressionProof: { 45 | defaultValue: k.noneValueNode() 46 | } 47 | } 48 | }, 49 | addPluginV1: { 50 | arguments: { 51 | initAuthority: { 52 | defaultValue: k.noneValueNode() 53 | } 54 | } 55 | }, 56 | addCollectionPluginV1: { 57 | arguments: { 58 | initAuthority: { 59 | defaultValue: k.noneValueNode() 60 | } 61 | } 62 | }, 63 | burnV1: { 64 | arguments: { 65 | compressionProof: { 66 | defaultValue: k.noneValueNode() 67 | } 68 | } 69 | }, 70 | createV1: { 71 | arguments: { 72 | plugins: { 73 | defaultValue: k.arrayValueNode([]) 74 | }, 75 | dataState: { 76 | defaultValue: k.enumValueNode('DataState', 'AccountState') 77 | } 78 | } 79 | }, 80 | createCollectionV1: { 81 | arguments: { 82 | plugins: { 83 | defaultValue: k.noneValueNode() 84 | 85 | } 86 | } 87 | }, 88 | collect: { 89 | accounts: { 90 | recipient1: { 91 | defaultValue: k.publicKeyValueNode("8AT6o8Qk5T9QnZvPThMrF9bcCQLTGkyGvVZZzHgCw11v") 92 | }, 93 | recipient2: { 94 | defaultValue: k.publicKeyValueNode("MmHsqX4LxTfifxoH8BVRLUKrwDn1LPCac6YcCZTHhwt") 95 | } 96 | } 97 | }, 98 | updateV1: { 99 | arguments: { 100 | newUpdateAuthority: { 101 | defaultValue: k.noneValueNode() 102 | } 103 | } 104 | } 105 | }) 106 | ); 107 | 108 | // Set ShankAccount discriminator. 109 | const key = (name) => ({ field: "key", value: k.enumValueNode("Key", name) }); 110 | kinobi.update( 111 | k.setAccountDiscriminatorFromFieldVisitor({ 112 | assetV1: key("AssetV1"), 113 | collectionV1: key("CollectionV1"), 114 | }) 115 | ); 116 | 117 | // Render Rust. 118 | const crateDir = path.join(clientDir, "rust"); 119 | const rustDir = path.join(clientDir, "rust", "src", "generated"); 120 | kinobi.accept( 121 | k.renderRustVisitor(rustDir, { 122 | formatCode: true, 123 | crateFolder: crateDir, 124 | }) 125 | ); 126 | 127 | 128 | // rewrite the account names for custom account data 129 | kinobi.update( 130 | new k.updateAccountsVisitor({ 131 | baseAssetV1: { 132 | name: "assetV1", 133 | }, 134 | baseCollectionV1: { 135 | name: "collectionV1", 136 | } 137 | }) 138 | ); 139 | 140 | // Render JavaScript. 141 | const jsDir = path.join(clientDir, "js", "src", "generated"); 142 | const prettier = require(path.join(clientDir, "js", ".prettierrc.json")); 143 | kinobi.accept(k.renderJavaScriptVisitor(jsDir, { 144 | prettier, 145 | internalNodes: [], 146 | customAccountData: [{ 147 | name: "assetV1", 148 | extract: true, 149 | }, { 150 | name: "collectionV1", 151 | extract: true, 152 | }, { 153 | name: "pluginRegistryV1", 154 | extract: true, 155 | }], 156 | })); 157 | -------------------------------------------------------------------------------- /options/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /options/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,msg 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction<'a>( 9 | program_id: &Pubkey, 10 | accounts: &'a [AccountInfo<'a>], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | msg!("process"); 14 | Processor::process(program_id, accounts, instruction_data) 15 | } 16 | -------------------------------------------------------------------------------- /options/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | decode_error::DecodeError, 3 | msg, 4 | program_error::{PrintProgramError, ProgramError}, 5 | }; 6 | use thiserror::Error; 7 | #[derive(Error, Clone, Debug, Eq, PartialEq)] 8 | pub enum OptionsError { 9 | // past deadlin 10 | #[error("Past Deadline")] 11 | PastDeadline, 12 | #[error("Before Deadline")] 13 | BeforeDeadline, 14 | } 15 | 16 | impl PrintProgramError for OptionsError { 17 | fn print(&self) { 18 | msg!(&self.to_string()); 19 | } 20 | } 21 | 22 | impl From for ProgramError { 23 | fn from(e: OptionsError) -> Self { 24 | ProgramError::Custom(e as u32) 25 | } 26 | } 27 | 28 | impl DecodeError for OptionsError { 29 | fn type_of() -> &'static str { 30 | "Options Error" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /options/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | pub mod entrypoint; 3 | pub mod error; 4 | pub mod instruction; 5 | pub mod processor; 6 | pub mod state; 7 | pub mod utils; 8 | pub use solana_program; 9 | 10 | solana_program::declare_id!("E2ConrgkPx3tc7oKbt4phBiBsKuXoh1FWBr82znSipX1"); 11 | -------------------------------------------------------------------------------- /options/program/src/state.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /random_numbers/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /random_numbers/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" 16 | rand = "0.8.5" -------------------------------------------------------------------------------- /random_numbers/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use thiserror::Error; 3 | 4 | #[derive(BorshDeserialize, BorshSerialize, Debug)] 5 | pub struct State { 6 | pub random_numbers : [f64; 256] 7 | } 8 | 9 | #[derive(Error, Debug)] 10 | pub enum Error { 11 | #[error("failed to read solana config file: ({0})")] 12 | ConfigReadError(std::io::Error), 13 | 14 | #[error("invalid config: ({0})")] 15 | InvalidConfig(String), 16 | 17 | #[error("serialization error: ({0})")] 18 | SerializationError(std::io::Error), 19 | 20 | #[error("solana client error: ({0})")] 21 | ClientError(#[from] solana_client::client_error::ClientError), 22 | 23 | #[error("error in public key derivation: ({0})")] 24 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 25 | } 26 | 27 | pub type Result = std::result::Result; -------------------------------------------------------------------------------- /random_numbers/client/src/utils.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshSerialize}; 2 | use crate::state::{State, Error, Result}; 3 | 4 | /// Determines and reports the size of greeting data. 5 | pub fn get_state_size() -> Result { 6 | let encoded = State {random_numbers: [0.0; 256]} 7 | .try_to_vec() 8 | .map_err(|e| Error::SerializationError(e))?; 9 | Ok(encoded.len()) 10 | } 11 | -------------------------------------------------------------------------------- /random_numbers/program/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /random_numbers/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rng_v1" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.10.21" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.2.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "0.9.3" 14 | sha2 = "0.10.2" 15 | murmur3 = "0.5.1" 16 | 17 | [lib] 18 | crate-type = ["cdylib", "lib"] 19 | -------------------------------------------------------------------------------- /random_numbers/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /random_numbers/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /random_numbers/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | // entrypoint has only one allowed instruction: GenerateRandom 8 | // this will generate 512 random u64's given the method specified in the 9 | // 'method' argument for that instruction (see instruction.rs for more detail) 10 | entrypoint!(process_instruction); 11 | fn process_instruction( 12 | program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | Processor::process(program_id, accounts, instruction_data) 17 | } -------------------------------------------------------------------------------- /random_numbers/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum RNGError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | } 11 | 12 | impl From for ProgramError { 13 | fn from(e: RNGError) -> Self { 14 | ProgramError::Custom(e as u32) 15 | } 16 | } -------------------------------------------------------------------------------- /random_numbers/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::error::RNGError::InvalidInstruction; 5 | use crate::state::{RNGMeta}; 6 | 7 | 8 | 9 | 10 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 11 | pub enum RNGInstruction { 12 | 13 | GenerateRandom { 14 | metadata: RNGMeta 15 | } 16 | } 17 | 18 | impl RNGInstruction { 19 | /// Unpacks a byte buffer into a [EscrowInstruction]. 20 | pub fn unpack(input: &[u8]) -> Result { 21 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 22 | Ok(match tag { 23 | 0 => Self::GenerateRandom { 24 | metadata: RNGMeta::try_from_slice(&rest)?, 25 | }, 26 | _ => return Err(InvalidInstruction.into()), 27 | }) 28 | } 29 | } -------------------------------------------------------------------------------- /random_numbers/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; -------------------------------------------------------------------------------- /random_numbers/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(BorshDeserialize, BorshSerialize, Debug)] 4 | pub struct State { 5 | pub random_numbers : [f64; 256] 6 | } 7 | 8 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 9 | pub enum RNGMethod { 10 | Xorshift, 11 | Hash, 12 | FastHash, 13 | None 14 | } 15 | 16 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 17 | pub struct RNGMeta { 18 | pub initial_seed : u64, 19 | pub method : RNGMethod 20 | } 21 | 22 | pub struct HashStruct { 23 | pub nonce : u64, 24 | pub initial_seed : u64 25 | } -------------------------------------------------------------------------------- /random_numbers/python/xorshift.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import uint32 3 | 4 | # return a length 32bit vector containing the binary representation of value 5 | def value_to_binary(value): 6 | binary_values = np.zeros(32) 7 | bin_string = bin(value)[2:] 8 | n_bits = len(bin_string) 9 | start_bit = 32 - n_bits 10 | for i in range(start_bit, 32): 11 | binary_values[i] = int(bin_string[i - start_bit]) 12 | 13 | return (binary_values).astype(int) 14 | 15 | def binary_vector_to_string(binary_vector): 16 | binary_string = "0b" 17 | for bit in binary_vector: 18 | binary_string += str(bit) 19 | 20 | return binary_string 21 | 22 | def binary_to_value(binary_vector): 23 | 24 | binary_string = binary_vector_to_string(binary_vector) 25 | return int(binary_string, 2) 26 | 27 | def make_shift_matrix(direction = "left", n = 1): 28 | 29 | L = np.zeros([32, 32]) 30 | for i in range(0, 31): 31 | L[i + 1, i] = 1 32 | 33 | Ln = L 34 | for i in range(n - 1): 35 | Ln = np.dot(L, Ln) 36 | 37 | if (direction == "left"): 38 | return Ln 39 | 40 | return Ln.T 41 | 42 | 43 | def make_xor_left_shift_matrix(n = 1): 44 | 45 | L = make_shift_matrix("left", n) 46 | 47 | for i in range(0, 32): 48 | Ln[i , i] = 1 49 | 50 | return Ln 51 | 52 | def make_xor_right_shift_matrix(n = 1): 53 | 54 | L = make_xor_left_shift_matrix(n) 55 | return L.T 56 | 57 | 58 | value = 1234567890 59 | binary_value = value_to_binary(value) 60 | 61 | L1 = make_xor_left_shift_matrix(13) 62 | R1 = make_xor_right_shift_matrix(17) 63 | R2 = make_xor_right_shift_matrix(5) 64 | 65 | LR_shifted = (np.dot(binary_value, np.dot(L1, np.dot(R1, R2)))%2).astype(int) 66 | 67 | temp_value = uint32(value) 68 | temp_value ^= uint32(temp_value << 13) 69 | temp_value ^= uint32(temp_value >> 17) 70 | temp_value ^= uint32(temp_value >> 5) 71 | 72 | print(binary_to_value(LR_shifted), uint32(temp_value)) 73 | 74 | 75 | -------------------------------------------------------------------------------- /shorts/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /shorts/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | arrayref = "0.3.7" 10 | bytemuck = { version = "1.13.1", features = ["derive"] } 11 | num-derive = "0.4" 12 | num-traits = "0.2" 13 | num_enum = "0.6.1" 14 | spl-discriminator = "0.1.0" 15 | spl-type-length-value = "0.3.0" 16 | spl-tlv-account-resolution = "0.4.0" 17 | solana-sdk = "1.16.7" 18 | solana-client = "1.16.7" 19 | solana-program = "1.16.7" 20 | spl-token = {version = "3.5.0", features = ["no-entrypoint"]} 21 | spl-token-2022 = {version = "0.9.0", features = ["no-entrypoint"]} 22 | borsh = "0.10.3" 23 | solana-transaction-status = "1.16.0" 24 | rand = "0.7.0" 25 | spl-associated-token-account = "2.0" 26 | enum-map = "1.1.1" 27 | thiserror = "1.0" 28 | async-trait = "0.1" 29 | futures-util = "0.3" 30 | -------------------------------------------------------------------------------- /shorts/client/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod state; 2 | 3 | use crate::state::TokenInstruction; 4 | use std::env; 5 | use std::str::{from_utf8, FromStr}; 6 | 7 | use borsh::{BorshDeserialize, BorshSerialize}; 8 | use solana_client::rpc_client::RpcClient; 9 | use solana_client::rpc_config::RpcSendTransactionConfig; 10 | use solana_program::pubkey::Pubkey; 11 | use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; 12 | use solana_sdk::compute_budget::ComputeBudgetInstruction; 13 | use solana_sdk::{ 14 | instruction::{AccountMeta, Instruction}, 15 | signature::Keypair, 16 | signer::keypair::read_keypair_file, 17 | signer::Signer, 18 | transaction::Transaction, 19 | }; 20 | use solana_transaction_status::UiTransactionEncoding; 21 | 22 | const URL: &str = "https://api.devnet.solana.com"; 23 | //const URL: &str = "http://127.0.0.1:8899"; 24 | 25 | const PROGRAM_PUBKEY: &str = "DQ8rUMXqfvD6HYVt15ci9xLETZZuz49z5Q1i8tXNWSdq"; 26 | 27 | fn main() { 28 | let args: Vec = env::args().collect(); 29 | let key_file = &args[1]; 30 | let function = &args[2]; 31 | 32 | if function == "init" { 33 | if let Err(err) = init(key_file) { 34 | eprintln!("{:?}", err); 35 | std::process::exit(1); 36 | } 37 | } 38 | } 39 | 40 | pub fn init(key_file: &String) -> std::result::Result<(), state::Error> { 41 | // (2) Create a new Keypair for the new account 42 | let wallet = read_keypair_file(key_file).unwrap(); 43 | 44 | // (3) Create RPC client to be used to talk to Solana cluster 45 | let connection = RpcClient::new(URL); 46 | 47 | let program = Pubkey::from_str(PROGRAM_PUBKEY).unwrap(); 48 | 49 | let (pda_account, _bump_seed) = Pubkey::find_program_address(&[b"pda"], &program); 50 | 51 | let instruction = Instruction::new_with_borsh( 52 | program, 53 | &TokenInstruction::Init, 54 | vec![ 55 | AccountMeta::new_readonly(wallet.pubkey(), true), 56 | AccountMeta::new(pda_account, false), 57 | AccountMeta::new(solana_sdk::system_program::id(), false), 58 | ], 59 | ); 60 | 61 | let signers = [&wallet]; 62 | let instructions = vec![instruction]; 63 | let recent_hash = connection.get_latest_blockhash()?; 64 | 65 | let txn = Transaction::new_signed_with_payer( 66 | &instructions, 67 | Some(&wallet.pubkey()), 68 | &signers, 69 | recent_hash, 70 | ); 71 | 72 | let signature = connection.send_and_confirm_transaction_with_spinner_and_config( 73 | &txn, 74 | CommitmentConfig { 75 | commitment: CommitmentLevel::Confirmed, 76 | }, 77 | RpcSendTransactionConfig { 78 | skip_preflight: true, 79 | preflight_commitment: None, 80 | encoding: None, 81 | max_retries: None, 82 | min_context_slot: None, 83 | }, 84 | )?; 85 | println!("signature: {}", signature); 86 | let response = connection.get_transaction(&signature, UiTransactionEncoding::Json)?; 87 | println!("result: {:#?}", response); 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /shorts/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | use spl_discriminator::{ArrayDiscriminator, SplDiscriminate}; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum Error { 8 | #[error("failed to read solana config file: ({0})")] 9 | ConfigReadError(std::io::Error), 10 | 11 | #[error("invalid config: ({0})")] 12 | InvalidConfig(String), 13 | 14 | #[error("serialization error: ({0})")] 15 | SerializationError(std::io::Error), 16 | 17 | #[error("solana client error: ({0})")] 18 | ClientError(#[from] solana_client::client_error::ClientError), 19 | 20 | #[error("error in public key derivation: ({0})")] 21 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 22 | } 23 | 24 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 25 | pub struct CreateMeta { 26 | pub name: String, 27 | pub uri: String, 28 | } 29 | 30 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 31 | pub enum TokenInstruction { 32 | Init, 33 | CreateCollection { args: CreateMeta }, 34 | CreateToken { args: CreateMeta }, 35 | } 36 | -------------------------------------------------------------------------------- /shorts/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | mpl-core-main 5 | -------------------------------------------------------------------------------- /shorts/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shorts" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.18.11" 10 | thiserror = "1.0.24" 11 | borsh = "1.4.0" 12 | mpl-core = "0.5.0" 13 | toml_edit = "^0.21" 14 | proc-macro-crate = "^3.1.0" 15 | shank = "0.4.2" 16 | spl-token = {version = "4.0.0", features = ["no-entrypoint"]} 17 | spl-discriminator = "0.2.2" 18 | spl-type-length-value = "0.4.3" 19 | spl-tlv-account-resolution = "0.6.3" 20 | spl-pod = "0.2.2" 21 | spl-token-2022 = {version = "3.0.2", features = ["no-entrypoint"]} 22 | spl-associated-token-account = {version = "3.0.2", features = ["no-entrypoint"]} 23 | 24 | [lib] 25 | crate-type = ["cdylib", "lib"] 26 | 27 | [patch.crates-io] 28 | mpl-core = { path = "./mpl-core-main/clients/rust/" } 29 | -------------------------------------------------------------------------------- /shorts/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /shorts/program/configs/kinobi.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const k = require("@metaplex-foundation/kinobi"); 3 | 4 | // Paths. 5 | const clientDir = path.join(__dirname, "..", "clients"); 6 | const idlDir = path.join(__dirname, "..", "idls"); 7 | 8 | // Instanciate Kinobi. 9 | const kinobi = k.createFromIdls([path.join(idlDir, "mpl_core.json")]); 10 | 11 | // Update programs. 12 | kinobi.update( 13 | k.updateProgramsVisitor({ 14 | mplCoreProgram: { name: "mplCore" }, 15 | }) 16 | ); 17 | 18 | 19 | kinobi.update( 20 | new k.updateAccountsVisitor({ 21 | assetV1: { 22 | name: "baseAssetV1", 23 | }, 24 | collectionV1: { 25 | name: "baseCollectionV1", 26 | } 27 | }) 28 | ); 29 | 30 | kinobi.update(new k.updateDefinedTypesVisitor({ 31 | authority: { 32 | name: "pluginAuthority" 33 | } 34 | })) 35 | 36 | // Update instructions with default values 37 | kinobi.update( 38 | k.updateInstructionsVisitor({ 39 | // create: { 40 | // bytesCreatedOnChain: k.bytesFromAccount("assetAccount"), 41 | // }, 42 | transferV1: { 43 | arguments: { 44 | compressionProof: { 45 | defaultValue: k.noneValueNode() 46 | } 47 | } 48 | }, 49 | addPluginV1: { 50 | arguments: { 51 | initAuthority: { 52 | defaultValue: k.noneValueNode() 53 | } 54 | } 55 | }, 56 | addCollectionPluginV1: { 57 | arguments: { 58 | initAuthority: { 59 | defaultValue: k.noneValueNode() 60 | } 61 | } 62 | }, 63 | burnV1: { 64 | arguments: { 65 | compressionProof: { 66 | defaultValue: k.noneValueNode() 67 | } 68 | } 69 | }, 70 | createV1: { 71 | arguments: { 72 | plugins: { 73 | defaultValue: k.arrayValueNode([]) 74 | }, 75 | dataState: { 76 | defaultValue: k.enumValueNode('DataState', 'AccountState') 77 | } 78 | } 79 | }, 80 | createCollectionV1: { 81 | arguments: { 82 | plugins: { 83 | defaultValue: k.noneValueNode() 84 | 85 | } 86 | } 87 | }, 88 | collect: { 89 | accounts: { 90 | recipient1: { 91 | defaultValue: k.publicKeyValueNode("8AT6o8Qk5T9QnZvPThMrF9bcCQLTGkyGvVZZzHgCw11v") 92 | }, 93 | recipient2: { 94 | defaultValue: k.publicKeyValueNode("MmHsqX4LxTfifxoH8BVRLUKrwDn1LPCac6YcCZTHhwt") 95 | } 96 | } 97 | }, 98 | updateV1: { 99 | arguments: { 100 | newUpdateAuthority: { 101 | defaultValue: k.noneValueNode() 102 | } 103 | } 104 | } 105 | }) 106 | ); 107 | 108 | // Set ShankAccount discriminator. 109 | const key = (name) => ({ field: "key", value: k.enumValueNode("Key", name) }); 110 | kinobi.update( 111 | k.setAccountDiscriminatorFromFieldVisitor({ 112 | assetV1: key("AssetV1"), 113 | collectionV1: key("CollectionV1"), 114 | }) 115 | ); 116 | 117 | // Render Rust. 118 | const crateDir = path.join(clientDir, "rust"); 119 | const rustDir = path.join(clientDir, "rust", "src", "generated"); 120 | kinobi.accept( 121 | k.renderRustVisitor(rustDir, { 122 | formatCode: true, 123 | crateFolder: crateDir, 124 | }) 125 | ); 126 | 127 | 128 | // rewrite the account names for custom account data 129 | kinobi.update( 130 | new k.updateAccountsVisitor({ 131 | baseAssetV1: { 132 | name: "assetV1", 133 | }, 134 | baseCollectionV1: { 135 | name: "collectionV1", 136 | } 137 | }) 138 | ); 139 | 140 | // Render JavaScript. 141 | const jsDir = path.join(clientDir, "js", "src", "generated"); 142 | const prettier = require(path.join(clientDir, "js", ".prettierrc.json")); 143 | kinobi.accept(k.renderJavaScriptVisitor(jsDir, { 144 | prettier, 145 | internalNodes: [], 146 | customAccountData: [{ 147 | name: "assetV1", 148 | extract: true, 149 | }, { 150 | name: "collectionV1", 151 | extract: true, 152 | }, { 153 | name: "pluginRegistryV1", 154 | extract: true, 155 | }], 156 | })); 157 | -------------------------------------------------------------------------------- /shorts/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /shorts/program/src/amm_state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{to_vec, BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | 4 | use crate::state; 5 | 6 | #[derive(Default, BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 7 | pub struct AMM { 8 | pub account_type: state::AccountType, 9 | pub base_mint: Pubkey, 10 | pub quote_mint: Pubkey, 11 | pub lp_mint: Pubkey, 12 | pub base_key: Pubkey, 13 | pub quote_key: Pubkey, 14 | pub fee: u16, 15 | pub num_data_accounts: u32, 16 | pub last_price: f32, 17 | pub lp_amount: u64, 18 | pub borrow_cost: u16, 19 | pub leverage_frac: u16, 20 | pub amm_base_amount: u64, 21 | pub amm_quote_amount: u64, 22 | pub short_base_amount: u64, 23 | pub long_quote_amount: u64, 24 | } 25 | 26 | pub fn get_amm_data_size() -> usize { 27 | let encoded = to_vec(&AMM::default()).unwrap(); 28 | 29 | encoded.len() 30 | } 31 | 32 | #[derive(Default, BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 33 | pub struct OHLCV { 34 | pub timestamp: i64, 35 | pub open: f32, 36 | pub high: f32, 37 | pub low: f32, 38 | pub close: f32, 39 | pub volume: f32, 40 | } 41 | 42 | pub fn get_candle_size() -> usize { 43 | let encoded = to_vec(&OHLCV::default()).unwrap(); 44 | 45 | encoded.len() 46 | } 47 | 48 | #[derive(Default, BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 49 | pub struct TimeSeriesData { 50 | pub account_type: state::AccountType, 51 | pub data: Vec, 52 | } 53 | 54 | pub fn get_price_data_size() -> usize { 55 | let encoded = to_vec(&TimeSeriesData::default()).unwrap(); 56 | 57 | encoded.len() 58 | } 59 | 60 | pub fn get_amm_seeds<'a>(base_mint: Pubkey, quote_mint: Pubkey, amm_seeds: &mut Vec) { 61 | let base_first = base_mint.to_string() < quote_mint.to_string(); 62 | 63 | if base_first { 64 | amm_seeds.push(base_mint); 65 | amm_seeds.push(quote_mint) 66 | } else { 67 | amm_seeds.push(quote_mint); 68 | amm_seeds.push(base_mint); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /shorts/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey, 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction<'a>( 9 | program_id: &Pubkey, 10 | accounts: &'a [AccountInfo<'a>], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | Processor::process(program_id, accounts, instruction_data) 14 | } 15 | -------------------------------------------------------------------------------- /shorts/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | decode_error::DecodeError, 3 | msg, 4 | program_error::{PrintProgramError, ProgramError}, 5 | }; 6 | use thiserror::Error; 7 | #[derive(Error, Clone, Debug, Eq, PartialEq)] 8 | pub enum OptionsError { 9 | // past deadlin 10 | #[error("Past Deadline")] 11 | PastDeadline, 12 | #[error("Before Deadline")] 13 | BeforeDeadline, 14 | } 15 | 16 | impl PrintProgramError for OptionsError { 17 | fn print(&self) { 18 | msg!(&self.to_string()); 19 | } 20 | } 21 | 22 | impl From for ProgramError { 23 | fn from(e: OptionsError) -> Self { 24 | ProgramError::Custom(e as u32) 25 | } 26 | } 27 | 28 | impl DecodeError for OptionsError { 29 | fn type_of() -> &'static str { 30 | "Options Error" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /shorts/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | pub mod amm; 3 | pub mod amm_state; 4 | pub mod core_shorts; 5 | pub mod entrypoint; 6 | pub mod error; 7 | pub mod instruction; 8 | pub mod processor; 9 | pub mod state; 10 | pub mod utils; 11 | pub use solana_program; 12 | 13 | solana_program::declare_id!("twy8tiAMy9TVpVTdnSJF9X9cgAgmKthsrKtcRZnzU7y"); 14 | -------------------------------------------------------------------------------- /shorts/program/src/processor.rs: -------------------------------------------------------------------------------- 1 | use crate::core_shorts; 2 | use crate::{amm, instruction::accounts::InitAccounts}; 3 | use borsh::BorshDeserialize; 4 | use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; 5 | use solana_program::{program::invoke_signed, program_error::ProgramError, sysvar::rent}; 6 | 7 | use crate::instruction::AMMInstruction; 8 | 9 | pub struct Processor; 10 | impl Processor { 11 | pub fn process<'a>( 12 | program_id: &Pubkey, 13 | accounts: &'a [AccountInfo<'a>], 14 | instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | msg!("Process"); 17 | let instruction = AMMInstruction::try_from_slice(instruction_data)?; 18 | 19 | match instruction { 20 | AMMInstruction::Init() => { 21 | msg!("Init"); 22 | Self::init(program_id, accounts) 23 | } 24 | 25 | AMMInstruction::InitAMM(args) => { 26 | msg!("Create AMM"); 27 | amm::init_amm(program_id, accounts, args) 28 | } 29 | 30 | AMMInstruction::Swap(args) => { 31 | msg!("Swap"); 32 | amm::perform_swap(program_id, accounts, args) 33 | } 34 | 35 | AMMInstruction::AddLiquidity(args) => { 36 | msg!("Add Liquidity"); 37 | amm::add_liquidity(program_id, accounts, args) 38 | } 39 | 40 | AMMInstruction::RemoveLiquidity(args) => { 41 | msg!("Remove Liquidity"); 42 | amm::remove_liquidity(program_id, accounts, args) 43 | } 44 | 45 | AMMInstruction::CreateCollection() => { 46 | msg!("Create Collection"); 47 | core_shorts::create_collection(program_id, accounts) 48 | } 49 | 50 | AMMInstruction::EnterShort(args) => { 51 | msg!("Buy Short"); 52 | core_shorts::buy_short(program_id, accounts, args) 53 | } 54 | AMMInstruction::ExitShort() => { 55 | msg!("Sell Short"); 56 | core_shorts::sell_short(program_id, accounts) 57 | } 58 | AMMInstruction::LiquidateShort() => { 59 | msg!("Liquidate Short"); 60 | core_shorts::liquidate_short(program_id, accounts) 61 | } 62 | AMMInstruction::EnterLong(args) => { 63 | msg!("Enter Long"); 64 | core_shorts::enter_long(program_id, accounts, args) 65 | } 66 | AMMInstruction::ExitLong() => { 67 | msg!("Exit Long"); 68 | core_shorts::exit_long(program_id, accounts) 69 | } 70 | AMMInstruction::LiquidateLong() => { 71 | msg!("Liquidate Long"); 72 | core_shorts::liquidate_long(program_id, accounts) 73 | } 74 | } 75 | } 76 | 77 | pub fn init<'a>(program_id: &Pubkey, accounts: &'a [AccountInfo<'a>]) -> ProgramResult { 78 | let ctx = InitAccounts::context(accounts)?; 79 | 80 | if !ctx.accounts.user.is_signer { 81 | return Err(ProgramError::MissingRequiredSignature); 82 | } 83 | 84 | let (_pda_account, bump_seed) = Pubkey::find_program_address(&[b"pda"], &program_id); 85 | 86 | let lamports = rent::Rent::default().minimum_balance(0); 87 | 88 | let ix = solana_program::system_instruction::create_account( 89 | ctx.accounts.user.key, 90 | ctx.accounts.program_pda.key, 91 | lamports, 92 | 0, 93 | program_id, 94 | ); 95 | 96 | invoke_signed( 97 | &ix, 98 | &[ctx.accounts.user.clone(), ctx.accounts.program_pda.clone()], 99 | &[&[b"pda", &[bump_seed]]], 100 | )?; 101 | 102 | Ok(()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /shorts/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(Default, BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 4 | pub enum AccountType { 5 | #[default] 6 | Launch, 7 | Program, 8 | User, 9 | Join, 10 | MMUserData, 11 | MMLaunchData, 12 | AMM, 13 | TimeSeries, 14 | CollectionLaunch, 15 | NFTAssignment, 16 | NFTLookup, 17 | } 18 | -------------------------------------------------------------------------------- /solana_streamer/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /solana_streamer/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" -------------------------------------------------------------------------------- /solana_streamer/client/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod state; 2 | 3 | use std::env; 4 | use std::str::FromStr; 5 | use crate::state::{Result, Choice, ChoiceData, ChoiceInstruction}; 6 | 7 | use solana_client::rpc_client::RpcClient; 8 | use solana_program::{pubkey::Pubkey}; 9 | use solana_sdk::{ 10 | signer::Signer, 11 | instruction::{Instruction}, 12 | transaction::Transaction, signer::keypair::read_keypair_file, 13 | }; 14 | use solana_transaction_status::UiTransactionEncoding; 15 | 16 | 17 | fn get_choice_from_int(index: u8) -> Choice { 18 | if index == 0 { 19 | return Choice::A; 20 | } 21 | else if index == 1 { 22 | return Choice::B; 23 | } 24 | else if index == 2 { 25 | return Choice::C; 26 | } 27 | else { 28 | return Choice::D; 29 | } 30 | } 31 | 32 | const URL: &str = "https://api.devnet.solana.com"; 33 | 34 | 35 | fn main() { 36 | 37 | let args: Vec = env::args().collect(); 38 | let key_file = &args[1]; 39 | let choice_string = &args[2]; 40 | let amount_string = &args[3]; 41 | 42 | let choice_index : u8 = choice_string.parse().unwrap(); 43 | let amount : u64 = amount_string.parse().unwrap(); 44 | let choice = get_choice_from_int(choice_index); 45 | 46 | if let Err(err) = make_choice(key_file, choice, amount) { 47 | eprintln!("{:?}", err); 48 | std::process::exit(1); 49 | } 50 | 51 | 52 | 53 | } 54 | 55 | fn make_choice(key_file: &String, choice: Choice, amount : u64) ->Result<()> { 56 | 57 | // (2) Create a new Keypair for the new account 58 | let wallet = read_keypair_file(key_file).unwrap(); 59 | 60 | // (3) Create RPC client to be used to talk to Solana cluster 61 | let connection = RpcClient::new(URL); 62 | 63 | let program = Pubkey::from_str("H73oSXtdJfuBz8JWwdqyG92D3txMqxPEhAhT23T8eHf5").unwrap(); 64 | 65 | let choice_data = ChoiceData{choice: choice, bid_amount: amount}; 66 | 67 | 68 | let make_choice_idx = Instruction::new_with_borsh( 69 | program, 70 | &ChoiceInstruction::MakeChoice{choice_data : choice_data}, 71 | vec![ 72 | ], 73 | ); 74 | 75 | // (7) Build transaction wrapping the create account instruction signed by both accounts 76 | let signers = [&wallet]; 77 | let instructions = vec![make_choice_idx]; 78 | let recent_hash = connection.get_latest_blockhash()?; 79 | 80 | let txn = Transaction::new_signed_with_payer( 81 | &instructions, 82 | Some(&wallet.pubkey()), 83 | &signers, 84 | recent_hash, 85 | ); 86 | 87 | // (8) Send transaction to the cluster and wait for confirmation 88 | let signature = connection.send_and_confirm_transaction(&txn)?; 89 | println!("signature: {}", signature); 90 | let response = connection.get_transaction(&signature, UiTransactionEncoding::Json)?; 91 | println!("result: {:#?}", response); 92 | 93 | Ok(println!("Success!")) 94 | } 95 | -------------------------------------------------------------------------------- /solana_streamer/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use thiserror::Error; 3 | 4 | 5 | #[derive(Error, Debug)] 6 | pub enum Error { 7 | 8 | #[error("solana client error: ({0})")] 9 | ClientError(#[from] solana_client::client_error::ClientError), 10 | 11 | } 12 | 13 | pub type Result = std::result::Result; 14 | 15 | 16 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 17 | pub enum ChoiceInstruction { 18 | 19 | // MakeChoice expects only one account, the user of the program which should be signed 20 | MakeChoice { 21 | choice_data: ChoiceData 22 | } 23 | } 24 | 25 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 26 | pub enum Choice { 27 | A, 28 | B, 29 | C, 30 | D 31 | } 32 | 33 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 34 | pub struct ChoiceData { 35 | pub choice : Choice, 36 | pub bid_amount : u64 37 | } -------------------------------------------------------------------------------- /solana_streamer/program/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /solana_streamer/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chooser_v1" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.10.21" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.2.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "0.9.3" 14 | 15 | 16 | [lib] 17 | crate-type = ["cdylib", "lib"] 18 | 19 | -------------------------------------------------------------------------------- /solana_streamer/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /solana_streamer/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /solana_streamer/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | // entrypoint has only one allowed instruction: GenerateRandom 8 | // this will generate 512 random u64's given the method specified in the 9 | // 'method' argument for that instruction (see instruction.rs for more detail) 10 | entrypoint!(process_instruction); 11 | fn process_instruction( 12 | program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | Processor::process(program_id, accounts, instruction_data) 17 | } -------------------------------------------------------------------------------- /solana_streamer/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum RNGError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | } 11 | 12 | impl From for ProgramError { 13 | fn from(e: RNGError) -> Self { 14 | ProgramError::Custom(e as u32) 15 | } 16 | } -------------------------------------------------------------------------------- /solana_streamer/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::error::RNGError::InvalidInstruction; 5 | use crate::state::{ChoiceData}; 6 | 7 | 8 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 9 | pub enum ChoiceInstruction { 10 | 11 | MakeChoice { 12 | choice_data: ChoiceData 13 | } 14 | } 15 | 16 | impl ChoiceInstruction { 17 | /// Unpacks a byte buffer into a [EscrowInstruction]. 18 | pub fn unpack(input: &[u8]) -> Result { 19 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 20 | Ok(match tag { 21 | 0 => Self::MakeChoice { 22 | choice_data: ChoiceData::try_from_slice(&rest)?, 23 | }, 24 | _ => return Err(InvalidInstruction.into()), 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solana_streamer/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; -------------------------------------------------------------------------------- /solana_streamer/program/src/processor.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize}; 2 | use crate::state::{ChoiceData}; 3 | 4 | 5 | use solana_program::{ 6 | account_info::{next_account_info, AccountInfo}, 7 | entrypoint::ProgramResult, 8 | pubkey::Pubkey,msg, 9 | program_error::ProgramError 10 | }; 11 | 12 | use crate::{instruction::ChoiceInstruction}; 13 | 14 | pub struct Processor; 15 | impl Processor { 16 | pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { 17 | 18 | let instruction = ChoiceInstruction::try_from_slice(&instruction_data[..])?; 19 | 20 | match instruction { 21 | ChoiceInstruction::MakeChoice { choice_data } => { 22 | 23 | Self::make_choice(program_id, accounts, choice_data) 24 | } 25 | } 26 | } 27 | 28 | 29 | fn make_choice( 30 | _program_id: &Pubkey, 31 | _accounts: &[AccountInfo], 32 | choice_data: ChoiceData 33 | ) ->ProgramResult { 34 | 35 | msg!("choice has been made: {:?} {}", choice_data.choice, choice_data.bid_amount); 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /solana_streamer/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | 4 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 5 | pub enum Choice { 6 | A, 7 | B, 8 | C, 9 | D 10 | } 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 13 | pub struct ChoiceData { 14 | pub choice : Choice, 15 | pub bid_amount : u64 16 | } 17 | -------------------------------------------------------------------------------- /solana_streamer/python/sql_funcs.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from sqlite3 import Error 3 | 4 | N_COLS = 4 5 | 6 | # setup the connection to the database and create the table if required 7 | def create_database_connection(): 8 | """ create a database connection to the SQLite database 9 | specified by db_file 10 | :param db_file: database file 11 | :return: Connection object or None 12 | """ 13 | 14 | db_file = r"solana_block_data.db" 15 | conn = None 16 | try: 17 | conn = sqlite3.connect(db_file, isolation_level=None) 18 | 19 | except Error as e: 20 | print(e) 21 | return None 22 | 23 | success = create_table(conn) 24 | 25 | if (not success): 26 | return None 27 | 28 | return conn 29 | 30 | # create the table with the structure required: 31 | # the primary key is just the row index 32 | # block_slot is the block the transaction is from 33 | # choice is the value of the enum the user sent to the program 34 | def create_table(conn): 35 | """ create a table from the create_table_sql statement 36 | :param conn: Connection object 37 | :return: 38 | """ 39 | 40 | create_signatures_table = """ CREATE TABLE IF NOT EXISTS signatures ( 41 | id int PRIMARY_KEY, 42 | block_slot int NOT NULL, 43 | choice string NOT NULL, 44 | bid_amount int NOT NULL); """ 45 | 46 | try: 47 | c = conn.cursor() 48 | c.execute(create_signatures_table) 49 | except Error as e: 50 | print(e) 51 | return False 52 | 53 | return True 54 | 55 | # inset a set of rows into the table within a single transaction 56 | def insert_rows(conn, rows): 57 | """ 58 | Create a new entry in the signatures table 59 | :param conn: 60 | :param row: 61 | :return: project id 62 | """ 63 | sql = ''' INSERT INTO signatures(id,block_slot,choice,bid_amount) 64 | VALUES(?,?,?,?) ''' 65 | cur = conn.cursor() 66 | cur.execute("begin") 67 | for row in rows: 68 | cur.execute(sql, row) 69 | cur.execute("commit") 70 | 71 | # returns the last row in the database, or None if it is empty 72 | def get_last_db_row(conn): 73 | 74 | # get the row that has the maximum value of id 75 | # this returns a vector that has the shape [row, max_id] 76 | # so we only return the first N_COLS=4 values 77 | 78 | cur = conn.cursor() 79 | cur.execute("SELECT *, max(id) FROM signatures") 80 | r = cur.fetchone() 81 | 82 | if (r[0] == None): 83 | return None 84 | 85 | return r[:N_COLS] -------------------------------------------------------------------------------- /solana_streamer/python/streamer.py: -------------------------------------------------------------------------------- 1 | from solana.rpc.api import Client 2 | import concurrent.futures as cf 3 | import numpy as np 4 | 5 | from sql_funcs import * 6 | from rpc_funcs import * 7 | 8 | 9 | db_conn = create_database_connection() 10 | # check the connection is valid 11 | if db_conn is None: 12 | print("Error! cannot create the database connection.") 13 | exit() 14 | 15 | # connect to solana node 16 | quick_node_dev = "MY_QUICK_NODE" 17 | 18 | dev_client = Client(quick_node_dev) 19 | 20 | if (not dev_client.is_connected()): 21 | print("Error! cannot connect to quicknode endpoint.") 22 | exit() 23 | 24 | 25 | current_row_id_to_insert = None 26 | current_block = None 27 | 28 | last_db_row = get_last_db_row(db_conn) 29 | 30 | if (last_db_row != None): 31 | print("getting current_block from DB: ") 32 | print(last_db_row) 33 | 34 | current_row_id_to_insert = last_db_row[0] + 1 35 | current_block = last_db_row[1] 36 | 37 | else: 38 | print("getting current_block from client") 39 | current_row_id_to_insert = 0 40 | current_block = get_slot(dev_client) 41 | 42 | print("Starting with row: ", current_row_id_to_insert, " Current block: ", current_block) 43 | while(True): 44 | 45 | # get all the blocks after and including current_block 46 | block_list = get_block_list(dev_client, current_block) 47 | 48 | # if the last block in the list was the current block, just wait and check again shortly 49 | if(block_list[-1] == current_block): 50 | time.sleep(0.05) 51 | continue 52 | 53 | # we are only interested in the blocks after current_block so remove that one from the list 54 | block_list = block_list[1:] 55 | 56 | # request all the blocks in block_list from the endpoint 57 | blocks = get_blocks(quick_node_dev, block_list) 58 | 59 | rows_to_insert = [] 60 | # if there is only one block in the list we don't need to do any multithreading, just get the transactions and process them 61 | if(len(block_list) == 1): 62 | 63 | b_idx, data = get_data_from_block(block_list[0], blocks[block_list[0]]) 64 | 65 | current_row_id_to_insert = create_rows_from_data(current_row_id_to_insert, b_idx, data, rows_to_insert) 66 | 67 | else: 68 | 69 | # if we have more than one block then multithread the requests and store them in a map with the block number as the key 70 | block_data = {} 71 | with cf.ThreadPoolExecutor(len(block_list)) as executor: 72 | futures = [executor.submit(get_data_from_block, block_id, blocks[block_id]) for block_id in block_list] 73 | 74 | for future in cf.as_completed(futures): 75 | # get the result for the next completed task 76 | b_result = future.result() # blocks 77 | block_data[b_result[0]] = b_result 78 | 79 | # once we have all the blocks process them in sequence so that they get stored in the database in sequential order 80 | for block_idx in block_list: 81 | 82 | b_idx, data = block_data[block_idx] 83 | 84 | current_row_id_to_insert = create_rows_from_data(current_row_id_to_insert, b_idx, data, rows_to_insert) 85 | 86 | insert_rows(db_conn, rows_to_insert) 87 | 88 | # update current_block to the last one in our list 89 | current_block = block_list[-1] -------------------------------------------------------------------------------- /token_2022/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /token_2022/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | arrayref = "0.3.7" 10 | bytemuck = { version = "1.13.1", features = ["derive"] } 11 | num-derive = "0.4" 12 | num-traits = "0.2" 13 | num_enum = "0.6.1" 14 | spl-discriminator = "0.1.0" 15 | spl-type-length-value = "0.3.0" 16 | spl-tlv-account-resolution = "0.4.0" 17 | solana-sdk = "1.16.7" 18 | solana-client = "1.16.7" 19 | solana-program = "1.16.7" 20 | spl-token = {version = "3.5.0", features = ["no-entrypoint"]} 21 | spl-token-2022 = {version = "0.9.0", features = ["no-entrypoint"]} 22 | borsh = "0.10.3" 23 | solana-transaction-status = "1.16.0" 24 | rand = "0.7.0" 25 | spl-associated-token-account = "2.0" 26 | enum-map = "1.1.1" 27 | thiserror = "1.0" 28 | async-trait = "0.1" 29 | futures-util = "0.3" 30 | -------------------------------------------------------------------------------- /token_2022/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | use spl_discriminator::{ArrayDiscriminator, SplDiscriminate}; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum Error { 8 | #[error("failed to read solana config file: ({0})")] 9 | ConfigReadError(std::io::Error), 10 | 11 | #[error("invalid config: ({0})")] 12 | InvalidConfig(String), 13 | 14 | #[error("serialization error: ({0})")] 15 | SerializationError(std::io::Error), 16 | 17 | #[error("solana client error: ({0})")] 18 | ClientError(#[from] solana_client::client_error::ClientError), 19 | 20 | #[error("error in public key derivation: ({0})")] 21 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 22 | } 23 | 24 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 25 | pub struct CreateMeta { 26 | pub extensions: u64, 27 | pub transfer_fee_bp: u16, 28 | pub transfer_fee_max: u64, 29 | pub interest_rate: i16, 30 | pub name: String, 31 | pub symbol: String, 32 | pub uri: String, 33 | } 34 | 35 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 36 | pub enum TokenInstruction { 37 | CreateToken { metadata: CreateMeta }, 38 | Test, 39 | } 40 | 41 | pub enum Extensions { 42 | None = 0, 43 | TransferFee = 1, 44 | PermanentDelegate = 2, 45 | InterestBearing = 4, 46 | NonTransferable = 8, 47 | DefaultState = 16, 48 | TransferHook = 32, 49 | MetaData = 64, 50 | Collection = 128, 51 | } 52 | 53 | /// Namespace for all programs implementing transfer-hook 54 | pub const NAMESPACE: &str = "spl-transfer-hook-interface"; 55 | 56 | /// Seed for the state 57 | const EXTRA_ACCOUNT_METAS_SEED: &[u8] = b"extra-account-metas"; 58 | 59 | /// Get the state address PDA 60 | pub fn get_extra_account_metas_address(mint: &Pubkey, program_id: &Pubkey) -> Pubkey { 61 | get_extra_account_metas_address_and_bump_seed(mint, program_id).0 62 | } 63 | 64 | /// Function used by programs implementing the interface, when creating the PDA, 65 | /// to also get the bump seed 66 | pub fn get_extra_account_metas_address_and_bump_seed( 67 | mint: &Pubkey, 68 | program_id: &Pubkey, 69 | ) -> (Pubkey, u8) { 70 | Pubkey::find_program_address(&collect_extra_account_metas_seeds(mint), program_id) 71 | } 72 | 73 | /// Function used by programs implementing the interface, when creating the PDA, 74 | /// to get all of the PDA seeds 75 | pub fn collect_extra_account_metas_seeds(mint: &Pubkey) -> [&[u8]; 2] { 76 | [EXTRA_ACCOUNT_METAS_SEED, mint.as_ref()] 77 | } 78 | 79 | /// Function used by programs implementing the interface, when creating the PDA, 80 | /// to sign for the PDA 81 | pub fn collect_extra_account_metas_signer_seeds<'a>( 82 | mint: &'a Pubkey, 83 | bump_seed: &'a [u8], 84 | ) -> [&'a [u8]; 3] { 85 | [EXTRA_ACCOUNT_METAS_SEED, mint.as_ref(), bump_seed] 86 | } 87 | 88 | #[derive(SplDiscriminate)] 89 | #[discriminator_hash_input("spl-transfer-hook-interface:execute")] 90 | pub struct ExecuteInstruction; 91 | -------------------------------------------------------------------------------- /token_2022/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | -------------------------------------------------------------------------------- /token_2022/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "token_2022" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.18.11" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "4.0.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "=1.4.0" 14 | serde = { version = "1.0.198", optional = true } 15 | spl-associated-token-account = {version = "3.0.2", features = ["no-entrypoint"]} 16 | spl-token-2022 = {version = "3.0.2", features = ["no-entrypoint"]} 17 | spl-discriminator = "0.2.2" 18 | spl-type-length-value = "0.4.3" 19 | spl-tlv-account-resolution = "0.6.3" 20 | spl-pod = "0.2.2" 21 | spl-token-metadata-interface = { version = "0.3.3"} 22 | 23 | [lib] 24 | crate-type = ["cdylib", "lib"] 25 | -------------------------------------------------------------------------------- /token_2022/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /token_2022/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /token_2022/program/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 4 | }; 5 | use spl_associated_token_account::get_associated_token_address_with_program_id; 6 | use spl_token_2022; 7 | 8 | pub fn check_system_program_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 9 | if account_info.key != &solana_program::system_program::ID { 10 | msg!( 11 | "expected system program {}", 12 | solana_program::system_program::ID 13 | ); 14 | return Err(ProgramError::InvalidAccountData); 15 | } 16 | 17 | return Ok(()); 18 | } 19 | 20 | pub fn check_token_program_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 21 | if account_info.key != &spl_token::id() { 22 | msg!("expected token program {}", spl_token::id()); 23 | return Err(ProgramError::InvalidAccountData); 24 | } 25 | 26 | return Ok(()); 27 | } 28 | 29 | pub fn check_token_program_2022_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 30 | if account_info.key != &spl_token_2022::id() { 31 | msg!("expected token 2022 program {}", spl_token_2022::id()); 32 | return Err(ProgramError::InvalidAccountData); 33 | } 34 | 35 | return Ok(()); 36 | } 37 | 38 | pub fn check_associated_token_program_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 39 | if account_info.key != &spl_associated_token_account::ID { 40 | msg!( 41 | "expected associated token program {}", 42 | spl_associated_token_account::ID 43 | ); 44 | return Err(ProgramError::InvalidAccountData); 45 | } 46 | 47 | return Ok(()); 48 | } 49 | 50 | pub fn check_program_data_account<'a>( 51 | account_info: &'a AccountInfo<'a>, 52 | program_id: &Pubkey, 53 | seed: Vec<&[u8]>, 54 | ) -> Result { 55 | if seed.len() == 1 { 56 | let (expected_data_account, bump_seed) = 57 | Pubkey::find_program_address(&[seed[0]], &program_id); 58 | 59 | // the third account is the user's token account 60 | if account_info.key != &expected_data_account { 61 | msg!("expected program data account {}", expected_data_account); 62 | return Err(ProgramError::InvalidAccountData); 63 | } 64 | 65 | return Ok(bump_seed); 66 | } 67 | 68 | let (expected_data_account, bump_seed) = 69 | Pubkey::find_program_address(&[seed[0], seed[1]], &program_id); 70 | 71 | // the third account is the user's token account 72 | if account_info.key != &expected_data_account { 73 | msg!("expected program data account {}", expected_data_account); 74 | return Err(ProgramError::InvalidAccountData); 75 | } 76 | 77 | return Ok(bump_seed); 78 | } 79 | 80 | pub fn check_token_account<'a>( 81 | account_info: &'a AccountInfo<'a>, 82 | mint_account_info: &'a AccountInfo<'a>, 83 | token_account_info: &'a AccountInfo<'a>, 84 | ) -> ProgramResult { 85 | let expected_token_account = get_associated_token_address_with_program_id( 86 | &account_info.key, 87 | &mint_account_info.key, 88 | &spl_token_2022::id(), 89 | ); 90 | 91 | // the third account is the user's token account 92 | if token_account_info.key != &expected_token_account { 93 | msg!( 94 | "expected token account {} for mint {} and account {}, recieved {}", 95 | expected_token_account, 96 | mint_account_info.key, 97 | account_info.key, 98 | token_account_info.key 99 | ); 100 | return Err(ProgramError::InvalidAccountData); 101 | } 102 | 103 | return Ok(()); 104 | } 105 | -------------------------------------------------------------------------------- /token_2022/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,msg 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction<'a>( 9 | program_id: &Pubkey, 10 | accounts: &'a [AccountInfo<'a>], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | msg!("process"); 14 | Processor::process(program_id, accounts, instruction_data) 15 | } 16 | -------------------------------------------------------------------------------- /token_2022/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum NewError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | #[error("Team Account Not Created")] 11 | TeamAccountNotCreated, 12 | #[error("User Doesn't own Team Token")] 13 | NoTeamTokens, 14 | #[error("Team Name too long")] 15 | InvalidTeamName, 16 | #[error("Invalid Token Mint")] 17 | InvalidTokenMint, 18 | #[error("Team Already Exists")] 19 | TeamAlreadyExists, 20 | } 21 | 22 | impl From for ProgramError { 23 | fn from(e: NewError) -> Self { 24 | ProgramError::Custom(e as u32) 25 | } 26 | } 27 | 28 | /// Errors that may be returned by the interface. 29 | #[derive(Clone, Debug, Eq, Error, PartialEq)] 30 | pub enum TransferHookError { 31 | /// Incorrect account provided 32 | #[error("Incorrect account provided")] 33 | IncorrectAccount, 34 | /// Mint has no mint authority 35 | #[error("Mint has no mint authority")] 36 | MintHasNoMintAuthority, 37 | /// Incorrect mint authority has signed the instruction 38 | #[error("Incorrect mint authority has signed the instruction")] 39 | IncorrectMintAuthority, 40 | /// Program called outside of a token transfer 41 | #[error("Program called outside of a token transfer")] 42 | ProgramCalledOutsideOfTransfer, 43 | } 44 | impl From for ProgramError { 45 | fn from(e: TransferHookError) -> Self { 46 | ProgramError::Custom(e as u32) 47 | } 48 | } -------------------------------------------------------------------------------- /token_2022/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::program_error::ProgramError; 3 | 4 | use crate::error::NewError::InvalidInstruction; 5 | use spl_discriminator::SplDiscriminate; 6 | 7 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 8 | pub struct CreateMeta { 9 | pub extensions: u64, 10 | pub transfer_fee_bp: u16, 11 | pub transfer_fee_max: u64, 12 | pub interest_rate: i16, 13 | pub name: String, 14 | pub symbol: String, 15 | pub uri: String, 16 | } 17 | 18 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 19 | pub struct TransferMeta { 20 | pub amount: u64, 21 | } 22 | 23 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 24 | pub enum TokenInstruction { 25 | CreateToken { metadata: CreateMeta }, 26 | Transfer { metadata: TransferMeta }, 27 | UpdateMeta, 28 | } 29 | 30 | impl TokenInstruction { 31 | /// Unpacks a byte buffer into a [EscrowInstruction]. 32 | pub fn unpack(input: &[u8]) -> Result { 33 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 34 | Ok(match tag { 35 | 0 => Self::CreateToken { 36 | metadata: CreateMeta::try_from_slice(&rest)?, 37 | }, 38 | 1 => Self::Transfer { 39 | metadata: TransferMeta::try_from_slice(&rest)?, 40 | }, 41 | 2 => Self::UpdateMeta, 42 | 43 | _ => return Err(InvalidInstruction.into()), 44 | }) 45 | } 46 | } 47 | 48 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 49 | pub enum TransferHookInstruction { 50 | /// Runs additional transfer logic. 51 | /// 52 | /// Accounts expected by this instruction: 53 | /// 54 | /// 0. `[]` Source account 55 | /// 1. `[]` Token mint 56 | /// 2. `[]` Destination account 57 | /// 3. `[]` Source account's owner/delegate 58 | /// 4. `[]` Validation account 59 | /// 5..5+M `[]` `M` additional accounts, written in validation account data 60 | /// 61 | Execute { 62 | /// Amount of tokens to transfer 63 | amount: u64, 64 | }, 65 | InitializeExtraAccountMetas, 66 | } 67 | 68 | /// TLV instruction type only used to define the discriminator. The actual data 69 | /// is entirely managed by `ExtraAccountMetaList`, and it is the only data contained 70 | /// by this type. 71 | #[derive(SplDiscriminate)] 72 | #[discriminator_hash_input("spl-transfer-hook-interface:execute")] 73 | pub struct ExecuteInstruction; 74 | 75 | /// TLV instruction type used to initialize extra account metas 76 | /// for the transfer hook 77 | #[derive(SplDiscriminate)] 78 | #[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")] 79 | pub struct InitializeExtraAccountMetaListInstruction; 80 | 81 | impl TransferHookInstruction { 82 | /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer. 83 | pub fn pack(&self) -> Vec { 84 | let mut buf = vec![]; 85 | match self { 86 | Self::Execute { amount } => { 87 | buf.extend_from_slice(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE); 88 | buf.extend_from_slice(&amount.to_le_bytes()); 89 | } 90 | Self::InitializeExtraAccountMetas => { 91 | buf.extend_from_slice( 92 | InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE, 93 | ); 94 | } 95 | }; 96 | buf 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /token_2022/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | pub mod entrypoint; 3 | pub mod error; 4 | pub mod instruction; 5 | pub mod processor; 6 | pub mod state; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /token_2022/program/src/state.rs: -------------------------------------------------------------------------------- 1 | // support the following extensions 2 | // transfer fee - 0 3 | // permanent delegate - 1 4 | // interest bearing - 2 5 | // non transferable - 4 6 | // default account state - 8 7 | 8 | pub enum Extensions { 9 | None = 0, 10 | TransferFee = 1, 11 | PermanentDelegate = 2, 12 | InterestBearing = 4, 13 | NonTransferable = 8, 14 | DefaultState = 16, 15 | TransferHook = 32, 16 | MetaData = 64, 17 | Collection = 128, 18 | } 19 | -------------------------------------------------------------------------------- /transfer_hook/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # other random things 17 | *.txt 18 | *.sh 19 | random_input 20 | -------------------------------------------------------------------------------- /transfer_hook/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.16.7" 10 | solana-client = "1.16.7" 11 | solana-program = "1.16.7" 12 | spl-token = {version = "3.5.0", features = ["no-entrypoint"]} 13 | spl-token-2022 = {version = "0.7", features = ["no-entrypoint"]} 14 | borsh = "0.10.3" 15 | thiserror = "1.0.40" 16 | solana-transaction-status = "1.16.0" 17 | rand = "0.7.0" 18 | spl-associated-token-account = "1.1.3" 19 | enum-map = "1.1.1" 20 | -------------------------------------------------------------------------------- /transfer_hook/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum Error { 7 | #[error("failed to read solana config file: ({0})")] 8 | ConfigReadError(std::io::Error), 9 | 10 | #[error("invalid config: ({0})")] 11 | InvalidConfig(String), 12 | 13 | #[error("serialization error: ({0})")] 14 | SerializationError(std::io::Error), 15 | 16 | #[error("solana client error: ({0})")] 17 | ClientError(#[from] solana_client::client_error::ClientError), 18 | 19 | #[error("error in public key derivation: ({0})")] 20 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 21 | } 22 | 23 | pub type Result = std::result::Result; 24 | 25 | 26 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 27 | pub enum TransferHookInstruction { 28 | /// Runs additional transfer logic. 29 | /// 30 | /// Accounts expected by this instruction: 31 | /// 32 | /// 0. `[]` Source account 33 | /// 1. `[]` Token mint 34 | /// 2. `[]` Destination account 35 | /// 3. `[]` Source account's owner/delegate 36 | /// 4. `[]` Validation account 37 | /// 5..5+M `[]` `M` additional accounts, written in validation account data 38 | /// 39 | Execute { 40 | /// Amount of tokens to transfer 41 | amount: u64, 42 | }, 43 | /// Initializes the extra account metas on an account, writing into 44 | /// the first open TLV space. 45 | /// 46 | /// Accounts expected by this instruction: 47 | /// 48 | /// 0. `[w]` Account with extra account metas 49 | /// 1. `[]` Mint 50 | /// 2. `[s]` Mint authority 51 | /// 3. `[]` System program 52 | /// 4..4+M `[]` `M` additional accounts, to be written to validation data 53 | /// 54 | InitializeExtraAccountMetas, 55 | } 56 | 57 | 58 | /// Namespace for all programs implementing transfer-hook 59 | pub const NAMESPACE: &str = "spl-transfer-hook-interface"; 60 | 61 | /// Seed for the state 62 | const EXTRA_ACCOUNT_METAS_SEED: &[u8] = b"extra-account-metas"; 63 | 64 | /// Get the state address PDA 65 | pub fn get_extra_account_metas_address(mint: &Pubkey, program_id: &Pubkey) -> Pubkey { 66 | get_extra_account_metas_address_and_bump_seed(mint, program_id).0 67 | } 68 | 69 | /// Function used by programs implementing the interface, when creating the PDA, 70 | /// to also get the bump seed 71 | pub fn get_extra_account_metas_address_and_bump_seed( 72 | mint: &Pubkey, 73 | program_id: &Pubkey, 74 | ) -> (Pubkey, u8) { 75 | Pubkey::find_program_address(&collect_extra_account_metas_seeds(mint), program_id) 76 | } 77 | 78 | /// Function used by programs implementing the interface, when creating the PDA, 79 | /// to get all of the PDA seeds 80 | pub fn collect_extra_account_metas_seeds(mint: &Pubkey) -> [&[u8]; 2] { 81 | [EXTRA_ACCOUNT_METAS_SEED, mint.as_ref()] 82 | } 83 | 84 | /// Function used by programs implementing the interface, when creating the PDA, 85 | /// to sign for the PDA 86 | pub fn collect_extra_account_metas_signer_seeds<'a>( 87 | mint: &'a Pubkey, 88 | bump_seed: &'a [u8], 89 | ) -> [&'a [u8]; 3] { 90 | [EXTRA_ACCOUNT_METAS_SEED, mint.as_ref(), bump_seed] 91 | } 92 | 93 | -------------------------------------------------------------------------------- /transfer_hook/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | -------------------------------------------------------------------------------- /transfer_hook/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "transfer_hook" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "=1.18.11" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "4.0.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "=1.4.0" 14 | serde = { version = "1.0.198", optional = true } 15 | spl-associated-token-account = {version = "3.0.2", features = ["no-entrypoint"]} 16 | spl-token-2022 = {version = "3.0.2", features = ["no-entrypoint"]} 17 | spl-discriminator = "0.2.2" 18 | spl-type-length-value = "0.4.3" 19 | spl-tlv-account-resolution = "0.6.3" 20 | spl-pod = "0.2.2" 21 | spl-token-metadata-interface = { version = "0.3.3"} 22 | bytemuck = { version = "1.13.1", features = ["derive"] } 23 | 24 | [lib] 25 | crate-type = ["cdylib", "lib"] 26 | -------------------------------------------------------------------------------- /transfer_hook/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /transfer_hook/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /transfer_hook/program/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use solana_program::pubkey::Pubkey; 2 | use solana_program::{ 3 | account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, 4 | }; 5 | use spl_associated_token_account::get_associated_token_address_with_program_id; 6 | use spl_token_2022; 7 | 8 | pub fn check_system_program_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 9 | if account_info.key != &solana_program::system_program::ID { 10 | msg!( 11 | "expected system program {}", 12 | solana_program::system_program::ID 13 | ); 14 | return Err(ProgramError::InvalidAccountData); 15 | } 16 | 17 | return Ok(()); 18 | } 19 | 20 | pub fn check_token_program_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 21 | if account_info.key != &spl_token::id() { 22 | msg!("expected token program {}", spl_token::id()); 23 | return Err(ProgramError::InvalidAccountData); 24 | } 25 | 26 | return Ok(()); 27 | } 28 | 29 | pub fn check_token_program_2022_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 30 | if account_info.key != &spl_token_2022::id() { 31 | msg!("expected token 2022 program {}", spl_token_2022::id()); 32 | return Err(ProgramError::InvalidAccountData); 33 | } 34 | 35 | return Ok(()); 36 | } 37 | 38 | pub fn check_associated_token_program_key<'a>(account_info: &'a AccountInfo<'a>) -> ProgramResult { 39 | if account_info.key != &spl_associated_token_account::ID { 40 | msg!( 41 | "expected associated token program {}", 42 | spl_associated_token_account::ID 43 | ); 44 | return Err(ProgramError::InvalidAccountData); 45 | } 46 | 47 | return Ok(()); 48 | } 49 | 50 | pub fn check_program_data_account<'a>( 51 | account_info: &'a AccountInfo<'a>, 52 | program_id: &Pubkey, 53 | seed: Vec<&[u8]>, 54 | ) -> Result { 55 | if seed.len() == 1 { 56 | let (expected_data_account, bump_seed) = 57 | Pubkey::find_program_address(&[seed[0]], &program_id); 58 | 59 | // the third account is the user's token account 60 | if account_info.key != &expected_data_account { 61 | msg!("expected program data account {}", expected_data_account); 62 | return Err(ProgramError::InvalidAccountData); 63 | } 64 | 65 | return Ok(bump_seed); 66 | } 67 | 68 | let (expected_data_account, bump_seed) = 69 | Pubkey::find_program_address(&[seed[0], seed[1]], &program_id); 70 | 71 | // the third account is the user's token account 72 | if account_info.key != &expected_data_account { 73 | msg!("expected program data account {}", expected_data_account); 74 | return Err(ProgramError::InvalidAccountData); 75 | } 76 | 77 | return Ok(bump_seed); 78 | } 79 | 80 | pub fn check_token_account<'a>( 81 | account_info: &'a AccountInfo<'a>, 82 | mint_account_info: &'a AccountInfo<'a>, 83 | token_account_info: &'a AccountInfo<'a>, 84 | ) -> ProgramResult { 85 | let expected_token_account = get_associated_token_address_with_program_id( 86 | &account_info.key, 87 | &mint_account_info.key, 88 | &spl_token_2022::id(), 89 | ); 90 | 91 | // the third account is the user's token account 92 | if token_account_info.key != &expected_token_account { 93 | msg!( 94 | "expected token account {} for mint {} and account {}, recieved {}", 95 | expected_token_account, 96 | mint_account_info.key, 97 | account_info.key, 98 | token_account_info.key 99 | ); 100 | return Err(ProgramError::InvalidAccountData); 101 | } 102 | 103 | return Ok(()); 104 | } 105 | -------------------------------------------------------------------------------- /transfer_hook/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction<'a>( 9 | program_id: &Pubkey, 10 | accounts: &'a [AccountInfo<'a>], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | Processor::process(program_id, accounts, instruction_data) 14 | } 15 | -------------------------------------------------------------------------------- /transfer_hook/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum NewError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction, 10 | #[error("Team Account Not Created")] 11 | TeamAccountNotCreated, 12 | #[error("User Doesn't own Team Token")] 13 | NoTeamTokens, 14 | #[error("Team Name too long")] 15 | InvalidTeamName, 16 | #[error("Invalid Token Mint")] 17 | InvalidTokenMint, 18 | #[error("Team Already Exists")] 19 | TeamAlreadyExists, 20 | } 21 | 22 | impl From for ProgramError { 23 | fn from(e: NewError) -> Self { 24 | ProgramError::Custom(e as u32) 25 | } 26 | } -------------------------------------------------------------------------------- /transfer_hook/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::program_error::ProgramError; 3 | 4 | use spl_discriminator::{ArrayDiscriminator, SplDiscriminate}; 5 | 6 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 7 | pub enum TransferHookInstruction { 8 | /// Runs additional transfer logic. 9 | /// 10 | /// Accounts expected by this instruction: 11 | /// 12 | /// 0. `[]` Source account 13 | /// 1. `[]` Token mint 14 | /// 2. `[]` Destination account 15 | /// 3. `[]` Source account's owner/delegate 16 | /// 4. `[]` Validation account 17 | /// 5..5+M `[]` `M` additional accounts, written in validation account data 18 | /// 19 | Execute { 20 | /// Amount of tokens to transfer 21 | amount: u64, 22 | }, 23 | /// Initializes the extra account metas on an account, writing into 24 | /// the first open TLV space. 25 | /// 26 | /// Accounts expected by this instruction: 27 | /// 28 | /// 0. `[w]` Account with extra account metas 29 | /// 1. `[]` Mint 30 | /// 2. `[s]` Mint authority 31 | /// 3. `[]` System program 32 | /// 4..4+M `[]` `M` additional accounts, to be written to validation data 33 | /// 34 | InitializeExtraAccountMetaList, 35 | } 36 | 37 | /// TLV instruction type only used to define the discriminator. 38 | #[derive(SplDiscriminate)] 39 | #[discriminator_hash_input("spl-transfer-hook-interface:execute")] 40 | pub struct ExecuteInstruction; 41 | 42 | /// TLV instruction type used to initialize extra account metas 43 | /// for the transfer hook 44 | #[derive(SplDiscriminate)] 45 | #[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")] 46 | pub struct InitializeExtraAccountMetaListInstruction; 47 | 48 | 49 | impl TransferHookInstruction { 50 | 51 | pub fn unpack(input: &[u8]) -> Result { 52 | if input.len() < ArrayDiscriminator::LENGTH { 53 | return Err(ProgramError::InvalidInstructionData); 54 | } 55 | let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH); 56 | Ok(match discriminator { 57 | ExecuteInstruction::SPL_DISCRIMINATOR_SLICE => { 58 | let amount = rest 59 | .get(..8) 60 | .and_then(|slice| slice.try_into().ok()) 61 | .map(u64::from_le_bytes) 62 | .ok_or(ProgramError::InvalidInstructionData)?; 63 | Self::Execute { amount } 64 | }, 65 | InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => { 66 | Self::InitializeExtraAccountMetaList 67 | }, 68 | _ => return Err(ProgramError::InvalidInstructionData), 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /transfer_hook/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | pub mod entrypoint; 3 | pub mod error; 4 | pub mod instruction; 5 | pub mod processor; 6 | pub mod state; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /transfer_hook/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Default)] 4 | pub struct MintData { 5 | pub count: u64, 6 | } 7 | 8 | pub fn get_mint_data_size() -> usize { 9 | 8 10 | } 11 | -------------------------------------------------------------------------------- /twitter_tokens/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana_rust_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | solana-sdk = "1.10.23" 10 | solana-client = "1.10.23" 11 | solana-program = "1.10.21" 12 | spl-token = {version = "3.1.1", features = ["no-entrypoint"]} 13 | borsh = "0.9.3" 14 | thiserror = "1.0.24" 15 | solana-transaction-status = "1.10.26" 16 | rand = "0.8.5" 17 | spl-associated-token-account = "1.0.5" 18 | enum-map = "1.1.1" 19 | 20 | -------------------------------------------------------------------------------- /twitter_tokens/client/bid_select.sh: -------------------------------------------------------------------------------- 1 | cargo run ~/Documents/Crypto/Solana/paper_wallets/test_wallets/2000.json place_bid 0 100000 100000 >> bid_output.txt 2 | cargo run ~/Documents/Crypto/Solana/paper_wallets/phantom_2.json select_winners >> select_output.txt 3 | sleep 60 4 | -------------------------------------------------------------------------------- /twitter_tokens/client/src/state.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use enum_map::{enum_map, Enum}; 4 | use solana_program::{pubkey::Pubkey}; 5 | 6 | 7 | #[derive(Error, Debug)] 8 | pub enum Error { 9 | #[error("failed to read solana config file: ({0})")] 10 | ConfigReadError(std::io::Error), 11 | 12 | #[error("invalid config: ({0})")] 13 | InvalidConfig(String), 14 | 15 | #[error("serialization error: ({0})")] 16 | SerializationError(std::io::Error), 17 | 18 | #[error("solana client error: ({0})")] 19 | ClientError(#[from] solana_client::client_error::ClientError), 20 | 21 | #[error("error in public key derivation: ({0})")] 22 | KeyDerivationError(#[from] solana_sdk::pubkey::PubkeyError), 23 | } 24 | 25 | pub type Result = std::result::Result; 26 | 27 | 28 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 29 | pub struct RegisterMeta { 30 | // the string of the id that contains the users pubkey 31 | pub tweet_id : u64 32 | } 33 | 34 | 35 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 36 | pub struct UserMeta { 37 | // the string of the id that contains the users pubkey 38 | pub user_id : u64 39 | } 40 | 41 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 42 | pub struct TokenMeta { 43 | // the amount of supporter tokens to be send to the program 44 | pub supporter_amount : u64 45 | } 46 | 47 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 48 | pub struct ErrorMeta { 49 | // the amount of supporter tokens to be send to the program 50 | pub error_code : u8 51 | } 52 | 53 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 54 | pub struct HashTagMeta { 55 | // the string of the id that contains the users pubkey 56 | pub tweet_id : u64, 57 | pub hashtag : String 58 | } 59 | 60 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 61 | pub struct HashTagRewardMeta { 62 | // the amount of supporter tokens to be sent to the user 63 | pub amount : u64, 64 | pub tweet_id : u64, 65 | pub hashtag : String 66 | } 67 | 68 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 69 | pub enum TwitterInstruction { 70 | 71 | InitProgram { 72 | metadata: TokenMeta 73 | }, 74 | Register { 75 | metadata : RegisterMeta 76 | }, 77 | CreateUserAccount { 78 | metadata : UserMeta 79 | }, 80 | NewFollower { 81 | metadata : UserMeta 82 | }, 83 | SetError { 84 | metadata : ErrorMeta 85 | }, 86 | CheckFollower, 87 | CheckHashTag { 88 | metadata : HashTagMeta 89 | }, 90 | SendTokens { 91 | metadata : HashTagRewardMeta 92 | }, 93 | CheckRetweet { 94 | metadata : HashTagMeta 95 | } 96 | } 97 | 98 | 99 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 100 | pub struct UserData { 101 | pub account_key : Pubkey, 102 | pub last_time : i64, 103 | pub follow : bool 104 | } 105 | 106 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 107 | pub struct IDMap { 108 | pub twitter_id : u64, 109 | pub error_code : u8 110 | } 111 | 112 | /// Determines and reports the size of user data. 113 | pub fn get_user_data_size() -> usize { 114 | let encoded = UserData {account_key: solana_program::system_program::id(), last_time: 0, follow: false} 115 | .try_to_vec().unwrap(); 116 | 117 | encoded.len() 118 | } 119 | 120 | pub fn get_id_map_size() -> usize { 121 | let encoded = IDMap {twitter_id: 0, error_code: 0} 122 | .try_to_vec().unwrap(); 123 | 124 | encoded.len() 125 | } -------------------------------------------------------------------------------- /twitter_tokens/client/src/utils.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daoplays/solana_examples/16c8cf076a68fcaa1f0571520483d7b57b22f1ff/twitter_tokens/client/src/utils.rs -------------------------------------------------------------------------------- /twitter_tokens/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | -------------------------------------------------------------------------------- /twitter_tokens/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "twitter_tokens_v08" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "1.11.1" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.3.0", features = ["no-entrypoint"]} 12 | spl-associated-token-account = {version = "1.0.5", features = ["no-entrypoint"]} 13 | 14 | arrayref = "0.3.6" 15 | borsh = "0.9.3" 16 | solana-security-txt = {version = "1.0.1", features = ["parser"] } 17 | 18 | 19 | [lib] 20 | crate-type = ["cdylib", "lib"] 21 | -------------------------------------------------------------------------------- /twitter_tokens/program/LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /twitter_tokens/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /twitter_tokens/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /twitter_tokens/program/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use spl_associated_token_account::get_associated_token_address; 2 | use solana_program::{pubkey::Pubkey, declare_id}; 3 | // functions to calculate expected public keys 4 | 5 | // This can also be replaced with pubkey ("CU8AequXiVdXyVKc7Vqg2jiBDJgPwapMbcBrm7EVnTtm") if you are on a recent sdk 6 | mod daoplays { 7 | use super::*; 8 | declare_id!("FxVpjJ5AGY6cfCwZQP5v8QBfS4J2NPa62HbGh1Fu2LpD"); 9 | } 10 | 11 | 12 | mod supporters_token_mint { 13 | use super::*; 14 | declare_id!("ESxUiMdmrZzJBk1JryJyVpD2ok9cheTV43H1HXEy8n5x"); 15 | } 16 | 17 | 18 | pub fn get_expected_daoplays_key() -> Pubkey 19 | { 20 | daoplays::ID 21 | } 22 | 23 | pub fn get_expected_supporter_token_mint_key() -> Pubkey 24 | { 25 | supporters_token_mint::ID 26 | } 27 | 28 | pub fn get_expected_daoplays_supporters_token_key() -> Pubkey 29 | { 30 | get_associated_token_address( 31 | &get_expected_daoplays_key(), 32 | &get_expected_supporter_token_mint_key() 33 | ) 34 | } 35 | 36 | pub fn get_pda_bump() -> u8 37 | { 38 | 255 39 | } 40 | 41 | pub fn get_expected_program_address_key(program_id : &Pubkey) -> (Pubkey, u8) 42 | { 43 | let program_address = Pubkey::create_program_address(&[b"token_account", &[get_pda_bump()]], &program_id).unwrap(); 44 | 45 | (program_address, get_pda_bump()) 46 | } 47 | 48 | pub fn get_expected_program_supporter_token_key(program_id : &Pubkey) -> Pubkey 49 | { 50 | get_associated_token_address( 51 | &get_expected_program_address_key(program_id).0, 52 | &get_expected_supporter_token_mint_key() 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /twitter_tokens/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | 8 | entrypoint!(process_instruction); 9 | fn process_instruction( 10 | program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | instruction_data: &[u8], 13 | ) -> ProgramResult { 14 | 15 | Processor::process(program_id, accounts, instruction_data) 16 | } -------------------------------------------------------------------------------- /twitter_tokens/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum DaoPlaysError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction 10 | } 11 | 12 | impl From for ProgramError { 13 | fn from(e: DaoPlaysError) -> Self { 14 | ProgramError::Custom(e as u32) 15 | } 16 | } -------------------------------------------------------------------------------- /twitter_tokens/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use crate::error::DaoPlaysError::InvalidInstruction; 4 | 5 | 6 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 7 | pub struct RegisterMeta { 8 | // the string of the id that contains the users pubkey 9 | pub tweet_id : u64 10 | } 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 13 | pub struct HashTagMeta { 14 | // the string of the id that contains the users pubkey 15 | pub tweet_id : u64, 16 | pub hashtag : String 17 | } 18 | 19 | 20 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 21 | pub struct UserMeta { 22 | // the string of the id that contains the users pubkey 23 | pub user_id : u64 24 | } 25 | 26 | 27 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 28 | pub struct ErrorMeta { 29 | // the amount of supporter tokens to be send to the program 30 | pub error_code : u8 31 | } 32 | 33 | 34 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 35 | pub struct TokenMeta { 36 | // the amount of supporter tokens to be sent to the user 37 | pub amount : u64 38 | } 39 | 40 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 41 | pub struct HashTagRewardMeta { 42 | // the amount of supporter tokens to be sent to the user 43 | pub amount : u64, 44 | pub tweet_id : u64, 45 | pub hashtag : String 46 | } 47 | 48 | 49 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 50 | pub enum TwitterInstruction { 51 | 52 | InitProgram { 53 | metadata: TokenMeta 54 | }, 55 | Register { 56 | metadata : RegisterMeta 57 | }, 58 | CreateUserAccount, 59 | NewFollower { 60 | metadata : UserMeta 61 | }, 62 | SetError { 63 | metadata : ErrorMeta 64 | }, 65 | CheckFollower, 66 | CheckHashTag { 67 | metadata : HashTagMeta 68 | }, 69 | SendTokens { 70 | metadata : HashTagRewardMeta 71 | }, 72 | CheckRetweet { 73 | metadata : HashTagMeta 74 | }, 75 | SetUserID { 76 | metadata : UserMeta 77 | } 78 | } 79 | 80 | impl TwitterInstruction { 81 | /// Unpacks a byte buffer into a [EscrowInstruction]. 82 | pub fn unpack(input: &[u8]) -> Result { 83 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 84 | Ok(match tag { 85 | 0 => Self::InitProgram { 86 | metadata: TokenMeta::try_from_slice(&rest)?, 87 | }, 88 | 1 => Self::Register { 89 | metadata: RegisterMeta::try_from_slice(&rest)?, 90 | }, 91 | 2 => Self::CreateUserAccount, 92 | 3 => Self::NewFollower { 93 | metadata: UserMeta::try_from_slice(&rest)?, 94 | }, 95 | 4 => Self::SetError { 96 | metadata: ErrorMeta::try_from_slice(&rest)?, 97 | }, 98 | 5 => Self::CheckFollower, 99 | 6 => Self::CheckHashTag { 100 | metadata: HashTagMeta::try_from_slice(&rest)?, 101 | }, 102 | 7 => Self::SendTokens { 103 | metadata: HashTagRewardMeta::try_from_slice(&rest)?, 104 | }, 105 | 8 => Self::CheckRetweet { 106 | metadata: HashTagMeta::try_from_slice(&rest)?, 107 | }, 108 | 9 => Self::SetUserID { 109 | metadata: UserMeta::try_from_slice(&rest)?, 110 | }, 111 | _ => return Err(InvalidInstruction.into()), 112 | }) 113 | } 114 | } -------------------------------------------------------------------------------- /twitter_tokens/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; 6 | pub mod accounts; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /twitter_tokens/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | pubkey::Pubkey, 4 | }; 5 | use solana_security_txt::security_txt; 6 | 7 | 8 | 9 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 10 | pub struct UserData { 11 | pub account_key : Pubkey, 12 | pub last_time : i64, 13 | pub follow : bool 14 | } 15 | 16 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 17 | pub struct IDMap { 18 | pub twitter_id : u64, 19 | pub error_code : u8 20 | } 21 | 22 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 23 | pub struct RewardMark { 24 | pub mark : bool 25 | } 26 | 27 | /// Determines and reports the size of user data. 28 | pub fn get_user_data_size() -> usize { 29 | let encoded = UserData {account_key: solana_program::system_program::id(), last_time: 0, follow: false} 30 | .try_to_vec().unwrap(); 31 | 32 | encoded.len() 33 | } 34 | 35 | pub fn get_id_map_size() -> usize { 36 | let encoded = IDMap {twitter_id: 0, error_code: 0} 37 | .try_to_vec().unwrap(); 38 | 39 | encoded.len() 40 | } 41 | 42 | pub fn get_mark_size() -> usize { 43 | let encoded = RewardMark {mark: false} 44 | .try_to_vec().unwrap(); 45 | 46 | encoded.len() 47 | } 48 | 49 | security_txt! { 50 | // Required fields 51 | name: "Example", 52 | project_url: "http://example.com", 53 | contacts: "email:example@example.com,link:https://example.com/security,discord:example#1234", 54 | policy: "https://github.com/solana-labs/solana/blob/master/SECURITY.md", 55 | 56 | // Optional Fields 57 | preferred_languages: "en,de", 58 | source_code: "https://github.com/example/example2", 59 | encryption: " 60 | -----BEGIN PGP PUBLIC KEY BLOCK----- 61 | Comment: Alice's OpenPGP certificate 62 | Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html 63 | 64 | mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U 65 | b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE 66 | ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy 67 | MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO 68 | dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4 69 | OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s 70 | E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb 71 | DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn 72 | 0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE= 73 | =iIGO 74 | -----END PGP PUBLIC KEY BLOCK----- 75 | ", 76 | auditors: "None", 77 | acknowledgements: " 78 | The following hackers could've stolen all our money but didn't: 79 | - Neodyme 80 | " 81 | } -------------------------------------------------------------------------------- /twitter_tokens/program/tests/integration.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "test-bpf")] 2 | 3 | use { 4 | assert_matches::*, 5 | solana_program::{ 6 | instruction::{AccountMeta, Instruction}, 7 | pubkey::Pubkey, 8 | }, 9 | solana_sdk::{signature::Signer, transaction::Transaction}, 10 | solana_validator::test_validator::*, 11 | }; 12 | 13 | #[test] 14 | fn test_validator_transaction() { 15 | solana_logger::setup_with_default("solana_program_runtime=debug"); 16 | let program_id = Pubkey::new_unique(); 17 | 18 | let (test_validator, payer) = TestValidatorGenesis::default() 19 | .add_program("bpf_program_template", program_id) 20 | .start(); 21 | let rpc_client = test_validator.get_rpc_client(); 22 | 23 | let blockhash = rpc_client.get_latest_blockhash().unwrap(); 24 | 25 | let mut transaction = Transaction::new_with_payer( 26 | &[Instruction { 27 | program_id, 28 | accounts: vec![AccountMeta::new(payer.pubkey(), false)], 29 | data: vec![1, 2, 3], 30 | }], 31 | Some(&payer.pubkey()), 32 | ); 33 | transaction.sign(&[&payer], blockhash); 34 | 35 | assert_matches!(rpc_client.send_and_confirm_transaction(&transaction), Ok(_)); 36 | } 37 | -------------------------------------------------------------------------------- /twitter_tokens/python/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.json 3 | -------------------------------------------------------------------------------- /twitter_tokens/python/error_codes.py: -------------------------------------------------------------------------------- 1 | # error codes start from 0 2 | NO_ERROR = 0 # everything is fine 3 | PUBKEY_MISMATCH = 1 # when registering the public key in the tweet doesn't match the public key of the wallet registering 4 | HASHTAG_MISMATCH = 2 # when sending a tweet with a hashtag, if the hashtags don't contain the expected one 5 | TWITTER_ID_MISMATCH = 3 # when the user id of the tweet with a hashtag doesn't match the user id of the account 6 | INVALID_HASHTAG = 4 # user has tried to submit an invalid hashtag 7 | NOT_FOLLOWING = 5 # the user asked for a follow reward but isn't following 8 | ALREADY_RETWEETED = 6 # the user tried to request a reward for a tweet already retweeted 9 | 10 | # info codes start from 100 11 | USER_ID_ACCOUNT_CREATED = 100 # we have set up the user id account 12 | USER_ID_ACCOUNT_INITED = 101 # we have inited the user id account -------------------------------------------------------------------------------- /twitter_tokens/python/log.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | class bcolors: 4 | HEADER = '\033[95m' 5 | OKBLUE = '\033[94m' 6 | OKCYAN = '\033[96m' 7 | OKGREEN = '\033[92m' 8 | WARNING = '\033[93m' 9 | FAIL = '\033[91m' 10 | ENDC = '\033[0m' 11 | BOLD = '\033[1m' 12 | UNDERLINE = '\033[4m' 13 | 14 | def log(string): 15 | print(str(datetime.datetime.utcnow()) + " " + string) 16 | 17 | def log_error(string): 18 | print(str(datetime.datetime.utcnow()) + bcolors.FAIL + " ERROR: " + string + bcolors.ENDC) 19 | 20 | def log_info(string): 21 | print(str(datetime.datetime.utcnow()) + bcolors.OKBLUE + " INFO: " + string + bcolors.ENDC) 22 | 23 | def log_db(string): 24 | print(str(datetime.datetime.utcnow()) + bcolors.OKGREEN + " DB: " + string + bcolors.ENDC) -------------------------------------------------------------------------------- /unity_example/program/.gitignore: -------------------------------------------------------------------------------- 1 | /*-dump.txt 2 | /*.so 3 | /target/ 4 | -------------------------------------------------------------------------------- /unity_example/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unity_example" 3 | version = "1.0.0" 4 | edition = "2021" 5 | license = "WTFPL" 6 | publish = false 7 | 8 | [dependencies] 9 | solana-program = "1.11.1" 10 | thiserror = "1.0.24" 11 | spl-token = {version = "3.3.0", features = ["no-entrypoint"]} 12 | arrayref = "0.3.6" 13 | borsh = "0.9.3" 14 | spl-associated-token-account = {version = "1.0.5", features = ["no-entrypoint"]} 15 | enum-map = "1.1.1" 16 | 17 | [lib] 18 | crate-type = ["cdylib", "lib"] 19 | -------------------------------------------------------------------------------- /unity_example/program/README.md: -------------------------------------------------------------------------------- 1 | ### Environment Setup 2 | 1. Install Rust from https://rustup.rs/ 3 | 2. Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool 4 | 5 | ### Build and test for program compiled natively 6 | ``` 7 | $ cargo build 8 | $ cargo test 9 | ``` 10 | 11 | ### Build and test the program compiled for BPF 12 | ``` 13 | $ cargo build-bpf 14 | $ cargo test-bpf 15 | ``` 16 | -------------------------------------------------------------------------------- /unity_example/program/scripts/patch.crates-io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Patches the SPL crates for developing against a local solana monorepo 4 | # 5 | 6 | here="$(dirname "$0")" 7 | 8 | solana_dir=$1 9 | if [[ -z $solana_dir ]]; then 10 | echo "Usage: $0 " 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 | -------------------------------------------------------------------------------- /unity_example/program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey 3 | }; 4 | 5 | use crate::processor::Processor; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction( 9 | program_id: &Pubkey, 10 | accounts: &[AccountInfo], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | Processor::process(program_id, accounts, instruction_data) 14 | } -------------------------------------------------------------------------------- /unity_example/program/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use solana_program::program_error::ProgramError; 3 | 4 | 5 | #[derive(Error, Debug, Copy, Clone)] 6 | pub enum NewError { 7 | /// Invalid instruction 8 | #[error("Invalid Instruction")] 9 | InvalidInstruction 10 | } 11 | 12 | impl From for ProgramError { 13 | fn from(e: NewError) -> Self { 14 | ProgramError::Custom(e as u32) 15 | } 16 | } -------------------------------------------------------------------------------- /unity_example/program/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::program_error::ProgramError; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | 4 | use crate::error::NewError::InvalidInstruction; 5 | use crate::state::{ScoreMeta}; 6 | 7 | 8 | 9 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 10 | pub enum UnityInstruction { 11 | 12 | UploadScore { 13 | metadata: ScoreMeta 14 | } 15 | } 16 | 17 | impl UnityInstruction { 18 | /// Unpacks a byte buffer into a [EscrowInstruction]. 19 | pub fn unpack(input: &[u8]) -> Result { 20 | let (tag, rest) = input.split_first().ok_or(InvalidInstruction)?; 21 | Ok(match tag { 22 | 23 | 0 => Self::UploadScore { 24 | metadata: ScoreMeta::try_from_slice(&rest)? 25 | }, 26 | 27 | _ => return Err(InvalidInstruction.into()), 28 | }) 29 | } 30 | } -------------------------------------------------------------------------------- /unity_example/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entrypoint; 2 | pub mod error; 3 | pub mod instruction; 4 | pub mod processor; 5 | pub mod state; -------------------------------------------------------------------------------- /unity_example/program/src/processor.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use crate::state::{get_score_meta_size, ScoreMeta}; 3 | use crate::state; 4 | use solana_program::{ 5 | account_info::{next_account_info, AccountInfo}, 6 | entrypoint::ProgramResult, 7 | pubkey::Pubkey,msg, 8 | program_error::ProgramError, 9 | program::invoke_signed, 10 | program::invoke, 11 | sysvar::rent, 12 | program_pack::Pack 13 | }; 14 | 15 | use spl_token::{instruction, state::{Mint}}; 16 | 17 | use spl_associated_token_account::{get_associated_token_address, instruction::create_associated_token_account}; 18 | 19 | use crate::error::NewError; 20 | use crate::{instruction::UnityInstruction}; 21 | 22 | pub struct Processor; 23 | impl Processor { 24 | 25 | pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { 26 | 27 | let instruction = UnityInstruction::try_from_slice(&instruction_data[..])?; 28 | 29 | match instruction { 30 | 31 | UnityInstruction::UploadScore {metadata} => { 32 | Self::upload_score(program_id, accounts, metadata) 33 | } 34 | 35 | } 36 | } 37 | 38 | pub fn create_program_account<'a>( 39 | funding_account: &AccountInfo<'a>, 40 | pda : &AccountInfo<'a>, 41 | program_id : &Pubkey, 42 | bump_seed : u8, 43 | data_size : usize, 44 | seed : &[u8] 45 | 46 | ) -> ProgramResult 47 | { 48 | 49 | // Check if the account has already been initialized 50 | if **pda.try_borrow_lamports()? > 0 { 51 | msg!("This account is already initialized. skipping"); 52 | return Ok(()); 53 | } 54 | 55 | msg!("Creating program derived account"); 56 | 57 | let space : u64 = data_size.try_into().unwrap(); 58 | let lamports = rent::Rent::default().minimum_balance(data_size); 59 | 60 | msg!("Require {} lamports for {} size data", lamports, data_size); 61 | let ix = solana_program::system_instruction::create_account( 62 | funding_account.key, 63 | pda.key, 64 | lamports, 65 | space, 66 | program_id, 67 | ); 68 | 69 | // Sign and submit transaction 70 | invoke_signed( 71 | &ix, 72 | &[funding_account.clone(), pda.clone()], 73 | &[&[seed, &[bump_seed]]] 74 | )?; 75 | 76 | Ok(()) 77 | } 78 | 79 | fn upload_score( 80 | program_id: &Pubkey, 81 | accounts: &[AccountInfo], 82 | metadata : ScoreMeta 83 | ) -> ProgramResult 84 | { 85 | 86 | let account_info_iter = &mut accounts.iter(); 87 | 88 | // This function expects to be passed six accounts, get them all first and then check their value is as expected 89 | let player_account_info = next_account_info(account_info_iter)?; 90 | 91 | let player_data_account_info = next_account_info(account_info_iter)?; 92 | 93 | let _system_program_account_info = next_account_info(account_info_iter)?; 94 | 95 | 96 | if !player_account_info.is_signer { 97 | return Err(ProgramError::MissingRequiredSignature); 98 | } 99 | 100 | let (expected_data_account,bump_seed) = Pubkey::find_program_address(&[&player_account_info.key.to_bytes()], &program_id); 101 | 102 | // the second account is the program data account 103 | if player_data_account_info.key != &expected_data_account 104 | { 105 | msg!("expected second account to be the program data account {}", expected_data_account); 106 | return Err(ProgramError::InvalidAccountData); 107 | } 108 | 109 | Self::create_program_account(player_account_info, 110 | player_data_account_info, 111 | program_id, 112 | bump_seed, 113 | state::get_score_meta_size(), 114 | &player_account_info.key.to_bytes() 115 | )?; 116 | 117 | let mut player_data = state::ScoreMeta::try_from_slice(&player_data_account_info.data.borrow())?; 118 | 119 | if metadata.high_score > player_data.high_score { 120 | player_data.high_score = metadata.high_score; 121 | player_data.serialize(&mut &mut player_data_account_info.data.borrow_mut()[..])?; 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /unity_example/program/src/state.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | 4 | 5 | #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)] 6 | pub struct ScoreMeta { 7 | // the total number of teams registered 8 | pub high_score : u64, 9 | } 10 | 11 | pub fn get_score_meta_size() -> usize { 12 | let encoded = ScoreMeta {high_score: 0} 13 | .try_to_vec().unwrap(); 14 | 15 | encoded.len() 16 | } --------------------------------------------------------------------------------