├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── rust-toolchain.toml ├── rustfmt.toml └── src └── program-rust ├── .gitignore ├── Cargo.toml ├── Xargo.toml └── src ├── constraints.rs ├── entrypoint.rs ├── error.rs ├── exchanger ├── aldrin │ ├── instruction.rs │ └── mod.rs ├── crema │ ├── instruction.rs │ └── mod.rs ├── cropper │ ├── instruction.rs │ └── mod.rs ├── mod.rs ├── raydium │ ├── instruction.rs │ └── mod.rs ├── serum_dex │ ├── instruction.rs │ ├── matching.rs │ ├── mod.rs │ ├── order.rs │ └── state.rs ├── spl_token_swap │ ├── instruction.rs │ └── mod.rs └── stable_swap │ ├── instruction.rs │ └── mod.rs ├── instruction.rs ├── lib.rs ├── parser ├── aldrin.rs ├── base.rs ├── crema.rs ├── cropper.rs ├── mod.rs ├── raydium.rs ├── serum_dex.rs ├── spl_token_swap.rs └── stable_swap.rs ├── processor.rs ├── spl_token ├── error.rs ├── instruction.rs └── mod.rs └── state.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | all_github_action_checks: 19 | runs-on: ubuntu-latest 20 | needs: 21 | - rustfmt 22 | - test 23 | steps: 24 | - run: echo "Done" 25 | rustfmt: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: stable 32 | override: true 33 | profile: minimal 34 | components: rustfmt 35 | - name: Run fmt 36 | run: | 37 | cargo fmt --all -- --check 38 | # This workflow contains a single job called "build" 39 | test: 40 | # The type of runner that the job will run on 41 | runs-on: ubuntu-latest 42 | 43 | # Steps represent a sequence of tasks that will be executed as part of the job 44 | steps: 45 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 46 | - uses: actions/checkout@v2 47 | 48 | - name: install solana cli 49 | run: | 50 | sh -c "$(curl -sSfL https://release.solana.com/v1.8.14/install)" 51 | echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH 52 | 53 | # Runs a set of commands using the runners shell 54 | - name: run test 55 | run: | 56 | cargo test-bpf --manifest-path=./src/program-rust/Cargo.toml 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.sw[po] 3 | /.cargo 4 | dist/ 5 | .env 6 | test-ledger/ 7 | .DS_Store 8 | *.log 9 | target/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | // Insert spaces when pressing `Tab`. This setting is overridden based on the file contents when `editor.detectIndentation` is on. 4 | "editor.insertSpaces": true, 5 | "editor.renderWhitespace": "selection", 6 | "cSpell.words": [ 7 | "bitflags", 8 | "coption", 9 | "entrypoint", 10 | "enumflags", 11 | "hardcode", 12 | "lamports", 13 | "msrm", 14 | "onesol", 15 | "Orderbook", 16 | "Raydium", 17 | "rustfmt", 18 | "serumdex", 19 | "spltokenswap", 20 | "stableswap", 21 | "sysvar", 22 | "sysvars", 23 | "tokenswap" 24 | ], 25 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "src/program-rust", 4 | ] 5 | 6 | 7 | [profile.dev] 8 | split-debuginfo = "unpacked" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1sol protocol 2 | 3 | ![Logo](https://cdn.jsdelivr.net/gh/solana-labs/ecosystem/img/1sol.svg) 4 | 5 | ![Github Actions](https://github.com/1sol-io/1sol-protocol/actions/workflows/main.yml/badge.svg?branch=master) 6 | 7 | Recently, there have been increasing number of projects focused on infrastructure building including DEXs, lending and DeFi platforms on Solana. The emergence of DEXs and building out aggregators will bring a lot of benefits for DeFi on the Solana ecosystem. In the field of DeFi, there have been more and more transactions that want to get onto to Solana via cross-chain, and currently we don't have a convenient way to do so. 8 | 9 | So we decided to create 1Sol. 10 | 11 | ## Goals 12 | 13 | - Go-To Trading Portal: Be the go-to trading portal for trading on Solana. 14 | - One-Stop Aggregation Service: Integrate all kinds of DeFi and DEX. 15 | - Fool-Proof Operation: Provides the average user with information on prices, slippage and costs of all DEX on the web. Users can choose for themselves the one path that best suits them to trade. 16 | 17 | ## Program Deployments 18 | 19 | | Program | Devnet | Mainnet Beta | 20 | | ------- | ------ | ------------ | 21 | |1solProtocol| `9Bj8zgNWT6UaNcXMgzMFrnH5Z13nQ6vFkRNxP743zZyr` | `1SoLTvbiicqXZ3MJmnTL2WYXKLYpuxwHpa4yYrVQaMZ` | 22 | 23 | ## TODO 24 | 25 | - Link with Solana's official swap. 26 | - Link with Serum swap. 27 | - Link with [dexlab](https://www.dexlab.space/). 28 | - Link with Falcomp. 29 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.62.0" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 -------------------------------------------------------------------------------- /src/program-rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ -------------------------------------------------------------------------------- /src/program-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "onesol-protocol" 3 | version = "0.4.3" 4 | authors = ["croath "] 5 | edition = "2018" 6 | exclude = ["js/**"] 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | [features] 10 | no-entrypoint = [] 11 | production = [] 12 | 13 | [dependencies] 14 | solana-program = "=1.10.29" 15 | thiserror = "1.0" 16 | arrayref = "0.3.6" 17 | num-derive = "0.3.3" 18 | num-traits = "0.2.14" 19 | num_enum = "0.5.6" 20 | lazy_static = "1.4.0" 21 | 22 | 23 | [dev-dependencies] 24 | solana-sdk = "*" 25 | serum_dex = { version = "0.5.4", features = ["no-entrypoint", "test"]} 26 | bs58 = "*" 27 | 28 | [lib] 29 | crate-type = ["cdylib", "lib"] 30 | doctest = false 31 | 32 | 33 | [package.metadata.docs.rs] 34 | targets = ["x86_64-unknown-linux-gnu"] 35 | -------------------------------------------------------------------------------- /src/program-rust/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] -------------------------------------------------------------------------------- /src/program-rust/src/constraints.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "production")] 2 | use std::env; 3 | 4 | #[cfg(feature = "production")] 5 | pub const OWNER_KEY: &str = env!("PROTOCOL_OWNER_FEE_ADDRESS"); 6 | #[cfg(not(feature = "production"))] 7 | pub const OWNER_KEY: &str = "change me"; 8 | 9 | // pub const BASE_SEED: [u8; 32] = [ 10 | // 49, 97, 50, 98, 51, 99, 52, 100, 111, 110, 101, 115, 111, 108, 95, 97, 117, 116, 104, 111, 114, 11 | // 105, 116, 121, 119, 54, 120, 55, 121, 56, 122, 57, 12 | // ]; 13 | -------------------------------------------------------------------------------- /src/program-rust/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | //! Program entrypoint 2 | 3 | use crate::{error::ProtocolError, processor::Processor}; 4 | use solana_program::{ 5 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, 6 | program_error::PrintProgramError, pubkey::Pubkey, 7 | }; 8 | 9 | entrypoint!(process_instruction); 10 | fn process_instruction( 11 | program_id: &Pubkey, 12 | accounts: &[AccountInfo], 13 | instruction_data: &[u8], 14 | ) -> ProgramResult { 15 | if let Err(error) = Processor::process(program_id, accounts, instruction_data) { 16 | // catch the error so we can print it 17 | error.print::(); 18 | return Err(error); 19 | } 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/program-rust/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types 2 | 3 | use num_derive::FromPrimitive; 4 | use solana_program::{ 5 | decode_error::DecodeError, 6 | msg, 7 | program_error::{PrintProgramError, ProgramError}, 8 | }; 9 | use thiserror::Error; 10 | 11 | /// OneSolResult 12 | pub type ProtocolResult = Result; 13 | 14 | #[macro_export] 15 | macro_rules! check_unreachable { 16 | () => {{ 17 | Err(ProtocolError::Unreachable) 18 | }}; 19 | } 20 | 21 | // pub(crate) use check_unreachable; 22 | 23 | /// Errors that may be returned by the OneSol program. 24 | #[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)] 25 | pub enum ProtocolError { 26 | /// Unknown error. 27 | #[error("Unknown error")] 28 | Unknown, 29 | /// Swap instruction exceeds desired slippage limit 30 | #[error("Swap instruction exceeds desired slippage limit")] 31 | ExceededSlippage, 32 | /// Address of the provided swap token account is incorrect. 33 | #[error("Address of the provided swap token account is incorrect")] 34 | IncorrectSwapAccount, 35 | /// Invalid instruction number passed in. 36 | #[error("Invalid instruction")] 37 | InvalidInstruction, 38 | 39 | /// The input token is invalid for swap. 40 | #[error("InvalidInput")] 41 | InvalidInput, 42 | 43 | /// The provided token account has a delegate. 44 | #[error("Token account has a delegate")] 45 | InvalidDelegate, 46 | 47 | /// The provided token account has a close authority. 48 | #[error("Token account has a close authority")] 49 | InvalidCloseAuthority, 50 | 51 | /// The owner of the input isn't set to the program address generated by the program. 52 | #[error("Input account owner is not the program address")] 53 | InvalidOwner, 54 | 55 | /// The program address provided doesn't match the value generated by the program. 56 | #[error("Invalid program address generated from nonce and key")] 57 | InvalidProgramAddress, 58 | 59 | /// The deserialized the account returned something besides State::Account. 60 | #[error("Deserialized account is not an SPL Token account")] 61 | ExpectedAccount, 62 | 63 | /// The provided token program does not match the token program expected by the swap 64 | #[error("The provided token program does not match the token program expected by the swap")] 65 | IncorrectTokenProgramId, 66 | 67 | /// ConversionFailure 68 | #[error("Conversion to u64 failed with an overflow or underflow")] 69 | ConversionFailure, 70 | 71 | /// Given pool token amount results in zero trading tokens 72 | #[error("Given pool token amount results in zero trading tokens")] 73 | ZeroTradingTokens, 74 | /// Internal error 75 | #[error("internal error")] 76 | InternalError, 77 | 78 | /// Dex Instruction Error 79 | #[error("dex market instruction error")] 80 | DexInstructionError, 81 | 82 | /// Dex Invoke Error 83 | #[error("dex market invoke error")] 84 | DexInvokeError, 85 | 86 | /// Dex Swap Error 87 | #[error("dex market swap error")] 88 | DexSwapError, 89 | 90 | /// Invalid expected amount tout 91 | #[error("invalid expect amount out")] 92 | InvalidExpectAmountOut, 93 | 94 | /// Invalid account flags 95 | #[error("invalid account flags")] 96 | InvalidAccountFlags, 97 | 98 | /// Invalid account flags 99 | #[error("account data borrow failed")] 100 | BorrowAccountDataError, 101 | 102 | /// Invalid Authority 103 | #[error("invalid authority")] 104 | InvalidAuthority, 105 | 106 | /// Invalid token account 107 | #[error("invalid token account")] 108 | InvalidTokenAccount, 109 | 110 | /// Invalid pc mint 111 | #[error("invalid Pc ")] 112 | InvalidPcMint, 113 | /// Invalid coin mint 114 | #[error("invalid Pc mint")] 115 | InvalidCoinMint, 116 | 117 | /// invalid token mint 118 | #[error("invalid token mint")] 119 | InvalidTokenMint, 120 | 121 | /// invalid pool mint 122 | #[error("invalid pool mint")] 123 | InvalidPoolMint, 124 | 125 | /// Init OpenOrders instruction error 126 | #[error("init open_orders instruction error")] 127 | InitOpenOrdersInstructionError, 128 | 129 | /// invoke error 130 | #[error("invoke error")] 131 | InvokeError, 132 | 133 | /// Invalid Nonce 134 | #[error("invalid nonce")] 135 | InvalidNonce, 136 | 137 | /// Invalid token program 138 | #[error("invalid token program")] 139 | InvalidTokenProgram, 140 | 141 | /// Invalid signer account 142 | #[error("invalid signer account")] 143 | InvalidSignerAccount, 144 | 145 | /// Invalid Account data 146 | #[error("invalid account data")] 147 | InvalidAccountData, 148 | 149 | /// Invalid Accounts length 150 | #[error("invalid accounts length")] 151 | InvalidAccountsLength, 152 | 153 | /// Unreachable 154 | #[error("unreachable")] 155 | Unreachable, 156 | 157 | /// readable account detect 158 | #[error("Readonly account")] 159 | ReadonlyAccount, 160 | 161 | /// Invalid source token balance 162 | #[error("invalid source balance")] 163 | InvalidSourceBalance, 164 | 165 | #[error("invalid spl-token-swap account")] 166 | InvalidSplTokenSwapInfoAccount, 167 | 168 | #[error("invalid serum-dex market account")] 169 | InvalidSerumDexMarketAccount, 170 | 171 | #[error("open_orders not found")] 172 | OpenOrdersNotFound, 173 | 174 | #[error("invalid open orders account data")] 175 | InvalidOpenOrdersAccountData, 176 | 177 | #[error("invalid open orders account")] 178 | InvalidOpenOrdersAccount, 179 | 180 | #[error("invalid stable-swap account")] 181 | InvalidStableSwapAccount, 182 | 183 | #[error("invalid stable-swap account state")] 184 | InvalidStableSwapAccountState, 185 | 186 | #[error("invalid clock account")] 187 | InvalidClockAccount, 188 | 189 | #[error("invalid rent account")] 190 | InvalidRentAccount, 191 | 192 | #[error("invalid amm-info account")] 193 | InvalidAmmInfoAccount, 194 | 195 | #[error("invalid dex-market-info account")] 196 | InvalidDexMarketInfoAccount, 197 | 198 | #[error("pack data failed")] 199 | PackDataFailed, 200 | 201 | #[error("not rent exempt")] 202 | NotRentExempt, 203 | 204 | #[error("invalid owner key")] 205 | InvalidOwnerKey, 206 | 207 | #[error("invalid token account delegate")] 208 | InvalidTokenAccountDelegate, 209 | 210 | #[error("invalid raydium amm-info account")] 211 | InvalidRaydiumAmmInfoAccount, 212 | 213 | #[error("invalid serum-dex program id")] 214 | InvalidSerumDexProgramId, 215 | 216 | #[error("invalid fee token account")] 217 | InvalidFeeTokenAccount, 218 | 219 | #[error("invalid crema swap account data")] 220 | InvalidCremaSwapAccountData, 221 | 222 | #[error("overflow")] 223 | Overflow, 224 | } 225 | impl From for ProgramError { 226 | fn from(e: ProtocolError) -> Self { 227 | ProgramError::Custom(e as u32) 228 | } 229 | } 230 | impl DecodeError for ProtocolError { 231 | fn type_of() -> &'static str { 232 | "OneSolError" 233 | } 234 | } 235 | 236 | impl PrintProgramError for ProtocolError { 237 | fn print(&self) 238 | where 239 | E: 'static + std::error::Error + DecodeError + PrintProgramError + num_traits::FromPrimitive, 240 | { 241 | match self { 242 | ProtocolError::Unknown => msg!("Error: Unknown"), 243 | ProtocolError::ExceededSlippage => msg!("Error: ExceededSlippage"), 244 | ProtocolError::IncorrectSwapAccount => msg!("Error: IncorrectSwapAccount"), 245 | ProtocolError::InvalidDelegate => msg!("Error: InvalidDelegate"), 246 | ProtocolError::InvalidCloseAuthority => msg!("Error: InvalidCloseAuthority"), 247 | ProtocolError::InvalidInstruction => msg!("Error: InvalidInstruction"), 248 | ProtocolError::InvalidInput => msg!("Error: InvalidInput"), 249 | ProtocolError::InvalidOwner => msg!("Error: InvalidOwner"), 250 | ProtocolError::InvalidProgramAddress => msg!("Error: InvalidProgramAddress"), 251 | ProtocolError::ExpectedAccount => msg!("Error: ExpectedAccount"), 252 | ProtocolError::IncorrectTokenProgramId => msg!("Error: IncorrectTokenProgramId"), 253 | ProtocolError::ConversionFailure => msg!("Error: ConversionFailure"), 254 | ProtocolError::ZeroTradingTokens => msg!("Error: ZeroTradingTokens"), 255 | ProtocolError::InternalError => msg!("Error: InternalError"), 256 | ProtocolError::DexInstructionError => msg!("Error: DexInstructionError"), 257 | ProtocolError::DexInvokeError => msg!("Error: DexInvokeError"), 258 | ProtocolError::DexSwapError => msg!("Error: DexSwapError"), 259 | ProtocolError::InvalidExpectAmountOut => msg!("Error: InvalidExpectAmountOut"), 260 | ProtocolError::InvalidAccountFlags => msg!("Error: InvalidAccountFlags"), 261 | ProtocolError::BorrowAccountDataError => msg!("Error: BorrowAccountDataError"), 262 | ProtocolError::InvalidAuthority => msg!("Error: InvalidAuthority"), 263 | ProtocolError::InvalidTokenAccount => msg!("Error: InvalidTokenAccount"), 264 | ProtocolError::InitOpenOrdersInstructionError => { 265 | msg!("Error: InitOpenOrdersInstructionError") 266 | } 267 | ProtocolError::InvokeError => msg!("Error: InvokeError"), 268 | ProtocolError::InvalidNonce => msg!("Error: InvalidNonce"), 269 | ProtocolError::InvalidTokenMint => msg!("Error: InvalidTokenMint"), 270 | ProtocolError::InvalidPcMint => msg!("Error: InvalidPcMint"), 271 | ProtocolError::InvalidCoinMint => msg!("Error: InvalidCoinMint"), 272 | ProtocolError::InvalidTokenProgram => msg!("Error: InvalidTokenProgram"), 273 | ProtocolError::InvalidSignerAccount => msg!("Error: InvalidSignerAccount"), 274 | ProtocolError::InvalidAccountData => msg!("Error: InvalidAccountData"), 275 | ProtocolError::InvalidAccountsLength => msg!("Error: InvalidAccountsLength"), 276 | ProtocolError::Unreachable => msg!("Error: Unreachable"), 277 | ProtocolError::ReadonlyAccount => msg!("Error: ReadonlyAccount"), 278 | ProtocolError::InvalidSourceBalance => msg!("Error: InvalidSourceBalance"), 279 | ProtocolError::InvalidSplTokenSwapInfoAccount => { 280 | msg!("Error: InvalidSplTokenSwapInfoAccount") 281 | } 282 | ProtocolError::InvalidSerumDexMarketAccount => msg!("Error: InvalidSerumDexMarketAccount"), 283 | ProtocolError::OpenOrdersNotFound => msg!("Error: OpenOrdersNotFound"), 284 | ProtocolError::InvalidOpenOrdersAccountData => { 285 | msg!("Error: InvalidOpenOrdersAccountData") 286 | } 287 | ProtocolError::InvalidStableSwapAccount => { 288 | msg!("Error: InvalidStableSwapAccount") 289 | } 290 | ProtocolError::InvalidStableSwapAccountState => { 291 | msg!("Error: InvalidStableSwapAccountState") 292 | } 293 | ProtocolError::InvalidClockAccount => { 294 | msg!("Error: InvalidClockAccount") 295 | } 296 | ProtocolError::InvalidRentAccount => { 297 | msg!("Error: InvalidRentAccount") 298 | } 299 | ProtocolError::InvalidAmmInfoAccount => { 300 | msg!("Error: InvalidAmmInfoAccount") 301 | } 302 | ProtocolError::PackDataFailed => { 303 | msg!("Error: PackDataFailed") 304 | } 305 | ProtocolError::NotRentExempt => { 306 | msg!("Error: NotRentExempt") 307 | } 308 | ProtocolError::InvalidDexMarketInfoAccount => { 309 | msg!("Error: InvalidDexMarketInfoAccount") 310 | } 311 | ProtocolError::InvalidOwnerKey => { 312 | msg!("Error: InvalidOwnerKey") 313 | } 314 | ProtocolError::InvalidTokenAccountDelegate => { 315 | msg!("Error: InvalidTokenAccountDelegate") 316 | } 317 | ProtocolError::InvalidRaydiumAmmInfoAccount => { 318 | msg!("Error: InvalidRaydiumAmmInfoAccount") 319 | } 320 | ProtocolError::InvalidSerumDexProgramId => { 321 | msg!("Error: InvalidSerumDexProgramId") 322 | } 323 | ProtocolError::InvalidFeeTokenAccount => { 324 | msg!("Error: InvalidFeeTokenAccount") 325 | } 326 | ProtocolError::InvalidOpenOrdersAccount => { 327 | msg!("Error: InvalidOpenOrdersAccount") 328 | } 329 | ProtocolError::InvalidCremaSwapAccountData => { 330 | msg!("Error: InvalidCremaSwapAccountData") 331 | } 332 | ProtocolError::InvalidPoolMint => { 333 | msg!("Error: InvalidPoolMint") 334 | } 335 | ProtocolError::Overflow => { 336 | msg!("Error: Overflow") 337 | } 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/aldrin/instruction.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 4 | use solana_program::{ 5 | instruction::{AccountMeta, Instruction}, 6 | program_error::ProgramError, 7 | pubkey::Pubkey, 8 | }; 9 | 10 | #[derive(Eq, PartialEq, Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug)] 11 | #[repr(u8)] 12 | pub enum Side { 13 | Bid = 0, 14 | Ask = 1, 15 | } 16 | 17 | #[derive(Clone, Debug, PartialEq)] 18 | struct Swap { 19 | /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 20 | pub amount_in: u64, 21 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 22 | pub minimum_amount_out: u64, 23 | pub side: Side, 24 | } 25 | 26 | #[derive(Debug, PartialEq)] 27 | enum SwapInstrution { 28 | Swap(Swap), 29 | } 30 | 31 | impl SwapInstrution { 32 | pub fn pack(&self) -> Vec { 33 | let mut buf = Vec::with_capacity(size_of::()); 34 | match &*self { 35 | Self::Swap(Swap { 36 | amount_in, 37 | minimum_amount_out, 38 | side, 39 | }) => { 40 | buf.extend_from_slice(&[248, 198, 158, 145, 225, 117, 135, 200]); 41 | buf.extend_from_slice(&amount_in.to_le_bytes()); 42 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 43 | buf.push(Side::into(*side)); 44 | } 45 | }; 46 | buf 47 | } 48 | } 49 | 50 | #[allow(clippy::too_many_arguments)] 51 | pub fn swap_instruction( 52 | program_id: &Pubkey, 53 | pool_key: &Pubkey, 54 | pool_signer: &Pubkey, 55 | pool_mint: &Pubkey, 56 | pool_coin_token_vault: &Pubkey, 57 | pool_pc_token_vault: &Pubkey, 58 | pool_fee_account: &Pubkey, 59 | pool_curve_key: &Pubkey, 60 | user_coin_token_account: &Pubkey, 61 | user_pc_token_account: &Pubkey, 62 | user_authority: &Pubkey, 63 | token_program_id: &Pubkey, 64 | amount_in: u64, 65 | minimum_amount_out: u64, 66 | side: Side, 67 | ) -> Result { 68 | let data = SwapInstrution::Swap(Swap { 69 | amount_in, 70 | minimum_amount_out, 71 | side, 72 | }) 73 | .pack(); 74 | 75 | let accounts = vec![ 76 | AccountMeta::new_readonly(*pool_key, false), 77 | AccountMeta::new_readonly(*pool_signer, false), 78 | AccountMeta::new(*pool_mint, false), 79 | AccountMeta::new(*pool_coin_token_vault, false), 80 | AccountMeta::new(*pool_pc_token_vault, false), 81 | AccountMeta::new(*pool_fee_account, false), 82 | AccountMeta::new_readonly(*user_authority, true), 83 | AccountMeta::new(*user_coin_token_account, false), 84 | AccountMeta::new(*user_pc_token_account, false), 85 | AccountMeta::new_readonly(*pool_curve_key, false), 86 | AccountMeta::new_readonly(*token_program_id, false), 87 | ]; 88 | 89 | Ok(Instruction { 90 | program_id: *program_id, 91 | accounts, 92 | data, 93 | }) 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | 100 | #[test] 101 | pub fn test_pack_swap_instruction() { 102 | let data = SwapInstrution::Swap(Swap { 103 | amount_in: 100, 104 | minimum_amount_out: 99, 105 | side: Side::Bid, 106 | }) 107 | .pack(); 108 | assert!( 109 | data 110 | == vec![ 111 | 248, 198, 158, 145, 225, 117, 135, 200, 100, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 112 | 0, 0 113 | ] 114 | ); 115 | let data = SwapInstrution::Swap(Swap { 116 | amount_in: 100, 117 | minimum_amount_out: 99, 118 | side: Side::Ask, 119 | }) 120 | .pack(); 121 | assert!( 122 | data 123 | == vec![ 124 | 248, 198, 158, 145, 225, 117, 135, 200, 100, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 125 | 0, 1 126 | ] 127 | ); 128 | let data = SwapInstrution::Swap(Swap { 129 | amount_in: 101, 130 | minimum_amount_out: 99, 131 | side: Side::Ask, 132 | }) 133 | .pack(); 134 | assert!( 135 | data 136 | != vec![ 137 | 248, 198, 158, 145, 225, 117, 135, 200, 100, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 138 | 0, 1 139 | ] 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/aldrin/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/crema/instruction.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | use solana_program::{ 4 | instruction::{AccountMeta, Instruction}, 5 | program_error::ProgramError, 6 | pubkey::Pubkey, 7 | }; 8 | 9 | #[derive(Clone, Debug, PartialEq)] 10 | struct Swap { 11 | /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 12 | pub amount_in: u64, 13 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 14 | pub minimum_amount_out: u64, 15 | } 16 | 17 | #[derive(Debug, PartialEq)] 18 | enum SwapInstrution { 19 | Swap(Swap), 20 | } 21 | 22 | impl SwapInstrution { 23 | pub fn pack(&self) -> Vec { 24 | let mut buf = Vec::with_capacity(size_of::()); 25 | match &*self { 26 | Self::Swap(Swap { 27 | amount_in, 28 | minimum_amount_out, 29 | }) => { 30 | buf.push(1); 31 | buf.extend_from_slice(&amount_in.to_le_bytes()); 32 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 33 | } 34 | }; 35 | buf 36 | } 37 | } 38 | 39 | #[allow(clippy::too_many_arguments)] 40 | pub fn swap_instruction( 41 | program_id: &Pubkey, 42 | swap_info_account: &Pubkey, 43 | swap_authority: &Pubkey, 44 | user_authority: &Pubkey, 45 | user_source_token_account: &Pubkey, 46 | user_destination_token_account: &Pubkey, 47 | pool_source_token_account: &Pubkey, 48 | pool_destination_token_account: &Pubkey, 49 | tick_dist_account: &Pubkey, 50 | token_program_id: &Pubkey, 51 | amount_in: u64, 52 | minimum_amount_out: u64, 53 | ) -> Result { 54 | let data = SwapInstrution::Swap(Swap { 55 | amount_in, 56 | minimum_amount_out, 57 | }) 58 | .pack(); 59 | // let swap_key = Pubkey::from_str(CREMA_SWAP_ACCOUNT).unwrap(); 60 | // let (authority, _) = Pubkey::find_program_address( 61 | // &[&swap_key.to_bytes()[..]], 62 | // &Pubkey::from_str(CREMA_PROGRAM_ID).unwrap(), 63 | // ); 64 | 65 | let accounts = vec![ 66 | AccountMeta::new(*swap_info_account, false), 67 | AccountMeta::new_readonly(*swap_authority, false), 68 | AccountMeta::new_readonly(*user_authority, true), 69 | AccountMeta::new(*user_source_token_account, false), 70 | AccountMeta::new(*user_destination_token_account, false), 71 | AccountMeta::new(*pool_source_token_account, false), 72 | AccountMeta::new(*pool_destination_token_account, false), 73 | AccountMeta::new(*tick_dist_account, false), 74 | AccountMeta::new_readonly(*token_program_id, false), 75 | ]; 76 | 77 | Ok(Instruction { 78 | program_id: *program_id, 79 | accounts, 80 | data, 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/crema/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/cropper/instruction.rs: -------------------------------------------------------------------------------- 1 | //! Instruction types 2 | 3 | #![allow(clippy::too_many_arguments)] 4 | 5 | use solana_program::{ 6 | instruction::{AccountMeta, Instruction}, 7 | program_error::ProgramError, 8 | pubkey::Pubkey, 9 | }; 10 | use std::mem::size_of; 11 | 12 | /// Swap instruction data 13 | #[cfg_attr(feature = "fuzz", derive(Arbitrary))] 14 | #[repr(C)] 15 | #[derive(Clone, Debug, PartialEq)] 16 | pub struct SwapInstruction { 17 | /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 18 | pub amount_in: u64, 19 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 20 | pub minimum_amount_out: u64, 21 | } 22 | 23 | /// Instructions supported by the token swap program. 24 | #[repr(C)] 25 | #[derive(Debug, PartialEq)] 26 | pub enum AmmInstruction { 27 | Swap(SwapInstruction), 28 | } 29 | 30 | impl AmmInstruction { 31 | /// Packs a [AmmInstruction](enum.AmmInstruction.html) into a byte buffer. 32 | pub fn pack(&self) -> Vec { 33 | let mut buf = Vec::with_capacity(size_of::()); 34 | match &*self { 35 | Self::Swap(SwapInstruction { 36 | amount_in, 37 | minimum_amount_out, 38 | }) => { 39 | buf.push(1); 40 | buf.extend_from_slice(&amount_in.to_le_bytes()); 41 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 42 | } 43 | }; 44 | buf 45 | } 46 | } 47 | 48 | /// Creates a 'swap' instruction. 49 | pub fn swap_instruction( 50 | program_id: &Pubkey, 51 | token_program_id: &Pubkey, 52 | swap_pubkey: &Pubkey, 53 | authority_pubkey: &Pubkey, 54 | user_transfer_authority_pubkey: &Pubkey, 55 | state_pubkey: &Pubkey, 56 | source_pubkey: &Pubkey, 57 | swap_source_pubkey: &Pubkey, 58 | swap_destination_pubkey: &Pubkey, 59 | destination_pubkey: &Pubkey, 60 | pool_mint_pubkey: &Pubkey, 61 | fee_account_pubkey: &Pubkey, 62 | amount_in: u64, 63 | minimum_amount_out: u64, 64 | ) -> Result { 65 | let data = AmmInstruction::Swap(SwapInstruction { 66 | amount_in, 67 | minimum_amount_out, 68 | }) 69 | .pack(); 70 | 71 | let accounts = vec![ 72 | AccountMeta::new_readonly(*swap_pubkey, false), 73 | AccountMeta::new_readonly(*authority_pubkey, false), 74 | AccountMeta::new_readonly(*user_transfer_authority_pubkey, true), 75 | AccountMeta::new_readonly(*state_pubkey, false), 76 | AccountMeta::new(*source_pubkey, false), 77 | AccountMeta::new(*swap_source_pubkey, false), 78 | AccountMeta::new(*swap_destination_pubkey, false), 79 | AccountMeta::new(*destination_pubkey, false), 80 | AccountMeta::new(*pool_mint_pubkey, false), 81 | AccountMeta::new(*fee_account_pubkey, false), 82 | AccountMeta::new_readonly(*token_program_id, false), 83 | ]; 84 | 85 | Ok(Instruction { 86 | program_id: *program_id, 87 | accounts, 88 | data, 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/cropper/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aldrin; 2 | pub mod crema; 3 | pub mod cropper; 4 | pub mod raydium; 5 | pub mod serum_dex; 6 | pub mod spl_token_swap; 7 | pub mod stable_swap; 8 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/raydium/instruction.rs: -------------------------------------------------------------------------------- 1 | //! Instruction types 2 | 3 | #![allow(clippy::too_many_arguments)] 4 | 5 | use solana_program::{ 6 | instruction::{AccountMeta, Instruction}, 7 | program_error::ProgramError, 8 | pubkey::Pubkey, 9 | }; 10 | use std::mem::size_of; 11 | 12 | use crate::spl_token; 13 | 14 | // #[repr(C)] 15 | // #[derive(Clone, Copy, Debug, Default, PartialEq)] 16 | // pub struct SwapInstructionBaseIn { 17 | // // SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 18 | // pub amount_in: u64, 19 | // } 20 | 21 | // #[repr(C)] 22 | // #[derive(Clone, Copy, Debug, Default, PartialEq)] 23 | // pub struct SwapInstructionBaseOut { 24 | // /// Minimum amount of DESTINATION token to output, prevents excessive slippage 25 | // pub amount_out: u64, 26 | // } 27 | 28 | #[repr(C)] 29 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 30 | pub struct SwapInstruction { 31 | // SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 32 | pub amount_in: u64, 33 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 34 | pub minimum_amount_out: u64, 35 | } 36 | 37 | /// Instructions supported by the AmmInfo program. 38 | #[repr(C)] 39 | #[derive(Clone, Debug, PartialEq)] 40 | pub enum AmmInstruction { 41 | /// Swap coin or pc from pool 42 | /// 43 | /// 0. `[]` Spl Token program id 44 | /// 1. `[writable]` amm Account 45 | /// 2. `[]` $authority 46 | /// 3. `[writable]` amm open_orders Account 47 | /// 4. `[writable]` amm target_orders Account 48 | /// 5. `[writable]` pool_token_coin Amm Account to swap FROM or To, 49 | /// 6. `[writable]` pool_token_pc Amm Account to swap FROM or To, 50 | /// 7. `[]` serum dex program id 51 | /// 8. `[writable]` serum market Account. serum_dex program is the owner. 52 | /// 9. `[writable]` bids Account 53 | /// 10. `[writable]` asks Account 54 | /// 11. `[writable]` event_q Account 55 | /// 12. `[writable]` coin_vault Account 56 | /// 13. `[writable]` pc_vault Account 57 | /// 14. '[]` vault_signer Account 58 | /// 15. `[writable]` user source token Account. user Account to swap from. 59 | /// 16. `[writable]` user destination token Account. user Account to swap to. 60 | /// 17. `[singer]` user owner Account 61 | SwapSlim(SwapInstruction), 62 | 63 | Swap(SwapInstruction), 64 | } 65 | 66 | impl AmmInstruction { 67 | /// Packs a [AmmInstruction](enum.AmmInstruction.html) into a byte buffer. 68 | pub fn pack(&self) -> Result, ProgramError> { 69 | let mut buf = Vec::with_capacity(size_of::()); 70 | match &*self { 71 | Self::SwapSlim(SwapInstruction { 72 | amount_in, 73 | minimum_amount_out, 74 | }) => { 75 | buf.push(9); 76 | buf.extend_from_slice(&amount_in.to_le_bytes()); 77 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 78 | } 79 | Self::Swap(SwapInstruction { 80 | amount_in, 81 | minimum_amount_out, 82 | }) => { 83 | buf.push(9); 84 | buf.extend_from_slice(&amount_in.to_le_bytes()); 85 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 86 | } 87 | } 88 | Ok(buf) 89 | } 90 | } 91 | 92 | /// Creates a 'swap base in' instruction. 93 | pub fn swap_slim( 94 | program_id: &Pubkey, 95 | amm_id: &Pubkey, 96 | amm_authority: &Pubkey, 97 | amm_open_orders: &Pubkey, 98 | pool_coin_token_account: &Pubkey, 99 | pool_pc_token_account: &Pubkey, 100 | serum_program_id: &Pubkey, 101 | serum_market: &Pubkey, 102 | serum_bids: &Pubkey, 103 | serum_asks: &Pubkey, 104 | serum_event_queue: &Pubkey, 105 | serum_coin_vault_account: &Pubkey, 106 | serum_pc_vault_account: &Pubkey, 107 | serum_vault_signer: &Pubkey, 108 | uer_source_token_account: &Pubkey, 109 | uer_destination_token_account: &Pubkey, 110 | user_source_owner: &Pubkey, 111 | amount_in: u64, 112 | minimum_amount_out: u64, 113 | ) -> Result { 114 | let data = AmmInstruction::SwapSlim(SwapInstruction { 115 | amount_in, 116 | minimum_amount_out, 117 | }) 118 | .pack()?; 119 | 120 | let accounts = vec![ 121 | // spl token 122 | AccountMeta::new_readonly(spl_token::id(), false), 123 | // amm 124 | AccountMeta::new(*amm_id, false), 125 | AccountMeta::new_readonly(*amm_authority, false), 126 | AccountMeta::new(*amm_open_orders, false), 127 | AccountMeta::new(*pool_coin_token_account, false), 128 | AccountMeta::new(*pool_pc_token_account, false), 129 | // serum 130 | AccountMeta::new_readonly(*serum_program_id, false), 131 | AccountMeta::new(*serum_market, false), 132 | AccountMeta::new(*serum_bids, false), 133 | AccountMeta::new(*serum_asks, false), 134 | AccountMeta::new(*serum_event_queue, false), 135 | AccountMeta::new(*serum_coin_vault_account, false), 136 | AccountMeta::new(*serum_pc_vault_account, false), 137 | AccountMeta::new_readonly(*serum_vault_signer, false), 138 | // user 139 | AccountMeta::new(*uer_source_token_account, false), 140 | AccountMeta::new(*uer_destination_token_account, false), 141 | AccountMeta::new_readonly(*user_source_owner, true), 142 | ]; 143 | 144 | Ok(Instruction { 145 | program_id: *program_id, 146 | accounts, 147 | data, 148 | }) 149 | } 150 | 151 | /// Creates a 'swap in' instruction. 152 | pub fn swap( 153 | program_id: &Pubkey, 154 | amm_id: &Pubkey, 155 | amm_authority: &Pubkey, 156 | amm_open_orders: &Pubkey, 157 | amm_target_orders: &Pubkey, 158 | pool_coin_token_account: &Pubkey, 159 | pool_pc_token_account: &Pubkey, 160 | serum_program_id: &Pubkey, 161 | serum_market: &Pubkey, 162 | serum_bids: &Pubkey, 163 | serum_asks: &Pubkey, 164 | serum_event_queue: &Pubkey, 165 | serum_coin_vault_account: &Pubkey, 166 | serum_pc_vault_account: &Pubkey, 167 | serum_vault_signer: &Pubkey, 168 | user_source_token_account: &Pubkey, 169 | user_destination_token_account: &Pubkey, 170 | user_source_owner: &Pubkey, 171 | amount_in: u64, 172 | minimum_amount_out: u64, 173 | ) -> Result { 174 | let data = AmmInstruction::Swap(SwapInstruction { 175 | amount_in, 176 | minimum_amount_out, 177 | }) 178 | .pack()?; 179 | 180 | let accounts = vec![ 181 | // spl token 182 | AccountMeta::new_readonly(spl_token::id(), false), 183 | // amm 184 | AccountMeta::new(*amm_id, false), 185 | AccountMeta::new_readonly(*amm_authority, false), 186 | AccountMeta::new(*amm_open_orders, false), 187 | AccountMeta::new(*amm_target_orders, false), 188 | AccountMeta::new(*pool_coin_token_account, false), 189 | AccountMeta::new(*pool_pc_token_account, false), 190 | // serum 191 | AccountMeta::new_readonly(*serum_program_id, false), 192 | AccountMeta::new(*serum_market, false), 193 | AccountMeta::new(*serum_bids, false), 194 | AccountMeta::new(*serum_asks, false), 195 | AccountMeta::new(*serum_event_queue, false), 196 | AccountMeta::new(*serum_coin_vault_account, false), 197 | AccountMeta::new(*serum_pc_vault_account, false), 198 | AccountMeta::new_readonly(*serum_vault_signer, false), 199 | // user 200 | AccountMeta::new(*user_source_token_account, false), 201 | AccountMeta::new(*user_destination_token_account, false), 202 | AccountMeta::new_readonly(*user_source_owner, true), 203 | ]; 204 | 205 | Ok(Instruction { 206 | program_id: *program_id, 207 | accounts, 208 | data, 209 | }) 210 | } 211 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/raydium/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/serum_dex/instruction.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU64; 2 | 3 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 4 | use solana_program::{ 5 | instruction::{AccountMeta, Instruction}, 6 | pubkey::Pubkey, 7 | sysvar::rent, 8 | }; 9 | 10 | use crate::error::ProtocolError; 11 | 12 | use super::matching::{OrderType, Side}; 13 | 14 | #[derive(PartialEq, Eq, Copy, Clone, Debug, TryFromPrimitive, IntoPrimitive)] 15 | #[repr(u8)] 16 | pub enum SelfTradeBehavior { 17 | DecrementTake = 0, 18 | CancelProvide = 1, 19 | AbortTransaction = 2, 20 | } 21 | 22 | #[derive(PartialEq, Eq, Debug, Clone)] 23 | pub struct NewOrderInstructionV3 { 24 | pub side: Side, 25 | pub limit_price: NonZeroU64, 26 | pub max_coin_qty: NonZeroU64, 27 | pub max_native_pc_qty_including_fees: NonZeroU64, 28 | pub self_trade_behavior: SelfTradeBehavior, 29 | pub order_type: OrderType, 30 | pub client_order_id: u64, 31 | pub limit: u16, 32 | } 33 | 34 | #[derive(PartialEq, Eq, Debug, Clone)] 35 | pub enum MarketInstruction { 36 | /// 0. `[writable]` market 37 | /// 1. `[writable]` OpenOrders 38 | /// 2. `[signer]` the OpenOrders owner 39 | /// 3. `[writable]` coin vault 40 | /// 4. `[writable]` pc vault 41 | /// 5. `[writable]` coin wallet 42 | /// 6. `[writable]` pc wallet 43 | /// 7. `[]` vault signer 44 | /// 8. `[]` spl token program 45 | /// 9. `[writable]` (optional) referrer pc wallet 46 | SettleFunds, 47 | /// 0. `[writable]` the market 48 | /// 1. `[writable]` the OpenOrders account to use 49 | /// 2. `[writable]` the request queue 50 | /// 3. `[writable]` the event queue 51 | /// 4. `[writable]` bids 52 | /// 5. `[writable]` asks 53 | /// 6. `[writable]` the (coin or price currency) account paying for the order 54 | /// 7. `[signer]` owner of the OpenOrders account 55 | /// 8. `[writable]` coin vault 56 | /// 9. `[writable]` pc vault 57 | /// 10. `[]` spl token program 58 | /// 11. `[]` the rent sysvar 59 | /// 12. `[]` (optional) the (M)SRM account used for fee discounts 60 | NewOrderV3(NewOrderInstructionV3), 61 | /// 0. `[writable]` OpenOrders 62 | /// 1. `[signer]` the OpenOrders owner 63 | /// 2. `[writable]` the destination account to send rent exemption SOL to 64 | /// 3. `[]` market 65 | CloseOpenOrders, 66 | /// 0. `[writable]` OpenOrders 67 | /// 1. `[signer]` the OpenOrders owner 68 | /// 2. `[]` market 69 | /// 3. `[]` 70 | /// 4. `[signer]` open orders market authority (optional). 71 | InitOpenOrders, 72 | } 73 | 74 | impl MarketInstruction { 75 | pub fn pack(&self) -> Vec { 76 | let mut buf = vec![0u8]; 77 | 78 | match &*self { 79 | Self::SettleFunds => { 80 | buf.extend_from_slice(&5u32.to_le_bytes()); 81 | } 82 | Self::NewOrderV3(NewOrderInstructionV3 { 83 | side, 84 | limit_price, 85 | max_coin_qty, 86 | max_native_pc_qty_including_fees, 87 | self_trade_behavior, 88 | order_type, 89 | client_order_id, 90 | limit, 91 | }) => { 92 | buf.extend_from_slice(&10u32.to_le_bytes()); 93 | let side_value: u8 = Side::into(*side); 94 | buf.extend_from_slice(&(side_value as u32).to_le_bytes()); 95 | buf.extend_from_slice(&limit_price.get().to_le_bytes()); 96 | buf.extend_from_slice(&max_coin_qty.get().to_le_bytes()); 97 | buf.extend_from_slice(&max_native_pc_qty_including_fees.get().to_le_bytes()); 98 | let self_trade_behavior_value: u8 = SelfTradeBehavior::into(*self_trade_behavior); 99 | buf.extend_from_slice(&(self_trade_behavior_value as u32).to_le_bytes()); 100 | let order_type_value: u8 = OrderType::into(*order_type); 101 | buf.extend_from_slice(&(order_type_value as u32).to_le_bytes()); 102 | buf.extend_from_slice(&client_order_id.to_le_bytes()); 103 | buf.extend_from_slice(&limit.to_le_bytes()); 104 | } 105 | Self::CloseOpenOrders => { 106 | buf.extend_from_slice(&14u32.to_le_bytes()); 107 | } 108 | Self::InitOpenOrders => { 109 | buf.extend_from_slice(&15u32.to_le_bytes()); 110 | } 111 | } 112 | buf 113 | } 114 | } 115 | 116 | #[allow(clippy::too_many_arguments)] 117 | pub fn new_order( 118 | market: &Pubkey, 119 | open_orders_account: &Pubkey, 120 | request_queue: &Pubkey, 121 | event_queue: &Pubkey, 122 | market_bids: &Pubkey, 123 | market_asks: &Pubkey, 124 | order_payer: &Pubkey, 125 | open_orders_account_owner: &Pubkey, 126 | coin_vault: &Pubkey, 127 | pc_vault: &Pubkey, 128 | spl_token_program_id: &Pubkey, 129 | rent_sysvar_id: &Pubkey, 130 | srm_account_referral: Option<&Pubkey>, 131 | program_id: &Pubkey, 132 | side: Side, 133 | limit_price: NonZeroU64, 134 | max_coin_qty: NonZeroU64, 135 | order_type: OrderType, 136 | client_order_id: u64, 137 | self_trade_behavior: SelfTradeBehavior, 138 | limit: u16, 139 | max_native_pc_qty_including_fees: NonZeroU64, 140 | ) -> Result { 141 | let data = MarketInstruction::NewOrderV3(NewOrderInstructionV3 { 142 | side, 143 | limit_price, 144 | max_coin_qty, 145 | order_type, 146 | client_order_id, 147 | self_trade_behavior, 148 | limit, 149 | max_native_pc_qty_including_fees, 150 | }) 151 | .pack(); 152 | let mut accounts = vec![ 153 | AccountMeta::new(*market, false), 154 | AccountMeta::new(*open_orders_account, false), 155 | AccountMeta::new(*request_queue, false), 156 | AccountMeta::new(*event_queue, false), 157 | AccountMeta::new(*market_bids, false), 158 | AccountMeta::new(*market_asks, false), 159 | AccountMeta::new(*order_payer, false), 160 | AccountMeta::new_readonly(*open_orders_account_owner, true), 161 | AccountMeta::new(*coin_vault, false), 162 | AccountMeta::new(*pc_vault, false), 163 | AccountMeta::new_readonly(*spl_token_program_id, false), 164 | AccountMeta::new_readonly(*rent_sysvar_id, false), 165 | ]; 166 | if let Some(key) = srm_account_referral { 167 | accounts.push(AccountMeta::new_readonly(*key, false)) 168 | } 169 | Ok(Instruction { 170 | program_id: *program_id, 171 | data, 172 | accounts, 173 | }) 174 | } 175 | 176 | #[allow(clippy::too_many_arguments)] 177 | pub fn settle_funds( 178 | program_id: &Pubkey, 179 | market: &Pubkey, 180 | spl_token_program_id: &Pubkey, 181 | open_orders_account: &Pubkey, 182 | open_orders_account_owner: &Pubkey, 183 | coin_vault: &Pubkey, 184 | coin_wallet: &Pubkey, 185 | pc_vault: &Pubkey, 186 | pc_wallet: &Pubkey, 187 | referrer_pc_wallet: Option<&Pubkey>, 188 | vault_signer: &Pubkey, 189 | ) -> Result { 190 | let data = MarketInstruction::SettleFunds.pack(); 191 | let mut accounts: Vec = vec![ 192 | AccountMeta::new(*market, false), 193 | AccountMeta::new(*open_orders_account, false), 194 | AccountMeta::new_readonly(*open_orders_account_owner, true), 195 | AccountMeta::new(*coin_vault, false), 196 | AccountMeta::new(*pc_vault, false), 197 | AccountMeta::new(*coin_wallet, false), 198 | AccountMeta::new(*pc_wallet, false), 199 | AccountMeta::new_readonly(*vault_signer, false), 200 | AccountMeta::new_readonly(*spl_token_program_id, false), 201 | ]; 202 | if let Some(key) = referrer_pc_wallet { 203 | accounts.push(AccountMeta::new(*key, false)) 204 | } 205 | Ok(Instruction { 206 | program_id: *program_id, 207 | data, 208 | accounts, 209 | }) 210 | } 211 | 212 | #[allow(dead_code)] 213 | pub fn close_open_orders( 214 | program_id: &Pubkey, 215 | open_orders: &Pubkey, 216 | owner: &Pubkey, 217 | destination: &Pubkey, 218 | market: &Pubkey, 219 | ) -> Result { 220 | let data = MarketInstruction::CloseOpenOrders.pack(); 221 | let accounts: Vec = vec![ 222 | AccountMeta::new(*open_orders, false), 223 | AccountMeta::new_readonly(*owner, true), 224 | AccountMeta::new(*destination, false), 225 | AccountMeta::new_readonly(*market, false), 226 | ]; 227 | Ok(Instruction { 228 | program_id: *program_id, 229 | data, 230 | accounts, 231 | }) 232 | } 233 | 234 | #[allow(dead_code)] 235 | pub fn init_open_orders( 236 | program_id: &Pubkey, 237 | open_orders: &Pubkey, 238 | owner: &Pubkey, 239 | market: &Pubkey, 240 | market_authority: Option<&Pubkey>, 241 | ) -> Result { 242 | let data = MarketInstruction::InitOpenOrders.pack(); 243 | let mut accounts: Vec = vec![ 244 | AccountMeta::new(*open_orders, false), 245 | AccountMeta::new_readonly(*owner, true), 246 | AccountMeta::new_readonly(*market, false), 247 | AccountMeta::new_readonly(rent::ID, false), 248 | ]; 249 | if let Some(market_authority) = market_authority { 250 | accounts.push(AccountMeta::new_readonly(*market_authority, true)); 251 | } 252 | Ok(Instruction { 253 | program_id: *program_id, 254 | data, 255 | accounts, 256 | }) 257 | } 258 | 259 | #[cfg(test)] 260 | mod tests { 261 | use super::*; 262 | 263 | #[test] 264 | pub fn test_pack_market_instruction_new_order() { 265 | let mi = MarketInstruction::NewOrderV3(NewOrderInstructionV3 { 266 | side: Side::Ask, 267 | limit_price: NonZeroU64::new(100).unwrap(), 268 | max_coin_qty: NonZeroU64::new(20).unwrap(), 269 | max_native_pc_qty_including_fees: NonZeroU64::new(109).unwrap(), 270 | self_trade_behavior: SelfTradeBehavior::AbortTransaction, 271 | order_type: OrderType::PostOnly, 272 | client_order_id: 33, 273 | limit: 65535, 274 | }); 275 | 276 | let mi2 = serum_dex::instruction::MarketInstruction::NewOrderV3( 277 | serum_dex::instruction::NewOrderInstructionV3 { 278 | side: serum_dex::matching::Side::Ask, 279 | limit_price: NonZeroU64::new(100).unwrap(), 280 | max_coin_qty: NonZeroU64::new(20).unwrap(), 281 | max_native_pc_qty_including_fees: NonZeroU64::new(109).unwrap(), 282 | self_trade_behavior: serum_dex::instruction::SelfTradeBehavior::AbortTransaction, 283 | order_type: serum_dex::matching::OrderType::PostOnly, 284 | client_order_id: 33, 285 | limit: 65535, 286 | }, 287 | ); 288 | 289 | assert!(mi.pack() == mi2.pack()); 290 | 291 | let mi = MarketInstruction::NewOrderV3(NewOrderInstructionV3 { 292 | side: Side::Bid, 293 | limit_price: NonZeroU64::new(100).unwrap(), 294 | max_coin_qty: NonZeroU64::new(20).unwrap(), 295 | max_native_pc_qty_including_fees: NonZeroU64::new(109).unwrap(), 296 | self_trade_behavior: SelfTradeBehavior::DecrementTake, 297 | order_type: OrderType::ImmediateOrCancel, 298 | client_order_id: 33, 299 | limit: 65535, 300 | }); 301 | 302 | let mi2 = serum_dex::instruction::MarketInstruction::NewOrderV3( 303 | serum_dex::instruction::NewOrderInstructionV3 { 304 | side: serum_dex::matching::Side::Bid, 305 | limit_price: NonZeroU64::new(100).unwrap(), 306 | max_coin_qty: NonZeroU64::new(20).unwrap(), 307 | max_native_pc_qty_including_fees: NonZeroU64::new(109).unwrap(), 308 | self_trade_behavior: serum_dex::instruction::SelfTradeBehavior::DecrementTake, 309 | order_type: serum_dex::matching::OrderType::ImmediateOrCancel, 310 | client_order_id: 33, 311 | limit: 65535, 312 | }, 313 | ); 314 | 315 | assert!(mi.pack() == mi2.pack()); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/serum_dex/matching.rs: -------------------------------------------------------------------------------- 1 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 2 | 3 | #[derive(Eq, PartialEq, Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug)] 4 | #[repr(u8)] 5 | pub enum Side { 6 | Bid = 0, 7 | Ask = 1, 8 | } 9 | 10 | #[derive(Eq, PartialEq, Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug)] 11 | #[repr(u8)] 12 | pub enum OrderType { 13 | Limit = 0, 14 | ImmediateOrCancel = 1, 15 | PostOnly = 2, 16 | } 17 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/serum_dex/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | pub mod matching; 3 | pub mod order; 4 | pub mod state; 5 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/serum_dex/order.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ProtocolError; 2 | //use serum_dex::{instruction, matching::Side, state::MarketState}; 3 | use solana_program::{ 4 | account_info::AccountInfo, 5 | entrypoint::ProgramResult, 6 | program::{invoke, invoke_signed}, 7 | pubkey::Pubkey, 8 | }; 9 | use std::num::NonZeroU64; 10 | 11 | use super::{ 12 | instruction::{self, init_open_orders, SelfTradeBehavior}, 13 | matching::{OrderType, Side}, 14 | state::MarketState, 15 | }; 16 | 17 | // An exchange rate for swapping *from* one token *to* another. 18 | #[derive(Clone, Debug, PartialEq, Eq, Copy)] 19 | pub struct ExchangeRate { 20 | // The amount of *to* tokens one should receive for a single *from token. 21 | // This number must be in native *to* units with the same amount of decimals 22 | // as the *to* mint. 23 | pub rate: u64, 24 | // Number of decimals of the *from* token's mint. 25 | pub from_decimals: u8, 26 | // Number of decimals of the *to* token's mint. 27 | // For a direct swap, this should be zero. 28 | pub quote_decimals: u8, 29 | // True if *all* of the *from* currency sold should be used when calculating 30 | // the executed exchange rate. 31 | // 32 | // To perform a transitive swap, one sells on one market and buys on 33 | // another, where both markets are quoted in the same currency. Now suppose 34 | // one swaps A for B across A/USDC and B/USDC. Further suppose the first 35 | // leg swaps the entire *from* amount A for USDC, and then only half of 36 | // the USDC is used to swap for B on the second leg. How should we calculate 37 | // the exchange rate? 38 | // 39 | // If strict is true, then the exchange rate will be calculated as a direct 40 | // function of the A tokens lost and B tokens gained, ignoring the surplus 41 | // in USDC received. If strict is false, an effective exchange rate will be 42 | // used. I.e. the surplus in USDC will be marked at the exchange rate from 43 | // the second leg of the swap and that amount will be added to the 44 | // *to* mint received before calculating the swap's exchange rate. 45 | // 46 | // Transitive swaps only. For direct swaps, this field is ignored. 47 | pub strict: bool, 48 | } 49 | 50 | // Market accounts are the accounts used to place orders against the dex minus 51 | // common accounts, i.e., program ids, sysvars, and the `pc_wallet`. 52 | #[derive(Clone)] 53 | pub struct MarketAccounts<'a, 'info: 'a> { 54 | pub market: &'a AccountInfo<'info>, 55 | pub open_orders: &'a AccountInfo<'info>, 56 | pub request_queue: &'a AccountInfo<'info>, 57 | pub event_queue: &'a AccountInfo<'info>, 58 | pub bids: &'a AccountInfo<'info>, 59 | pub asks: &'a AccountInfo<'info>, 60 | pub order_payer_authority: &'a AccountInfo<'info>, 61 | // Also known as the "base" currency. For a given A/B market, 62 | // this is the vault for the A mint. 63 | pub coin_vault: &'a AccountInfo<'info>, 64 | // Also known as the "quote" currency. For a given A/B market, 65 | // this is the vault for the B mint. 66 | pub pc_vault: &'a AccountInfo<'info>, 67 | // PDA owner of the DEX's token accounts for base + quote currencies. 68 | pub vault_signer: &'a AccountInfo<'info>, 69 | // User wallets. 70 | pub coin_wallet: &'a AccountInfo<'info>, 71 | } 72 | 73 | #[derive(Clone)] 74 | pub struct OrderbookClient<'a, 'info: 'a> { 75 | pub market: MarketAccounts<'a, 'info>, 76 | pub open_order_authority: &'a AccountInfo<'info>, 77 | pub pc_wallet: &'a AccountInfo<'info>, 78 | pub dex_program: &'a AccountInfo<'info>, 79 | pub token_program: &'a AccountInfo<'info>, 80 | pub rent: &'a AccountInfo<'info>, 81 | } 82 | 83 | impl<'a, 'info: 'a> OrderbookClient<'a, 'info> { 84 | // Executes the sell order portion of the swap, purchasing as much of the 85 | // quote currency as possible for the given `base_amount`. 86 | // 87 | // `base_amount` is the "native" amount of the base currency, i.e., token 88 | // amount including decimals. 89 | pub fn sell( 90 | &self, 91 | base_amount: u64, 92 | srm_msrm_discount: Option>, 93 | ) -> ProgramResult { 94 | let limit_price = 1; 95 | let max_coin_qty = { 96 | // The loaded market must be dropped before CPI. 97 | let market = MarketState::unpack_from_slice(&self.market.market.try_borrow_data()?)?; 98 | coin_lots(&market, base_amount) 99 | }; 100 | let max_native_pc_qty = u64::MAX; 101 | self.order_cpi( 102 | limit_price, 103 | max_coin_qty, 104 | max_native_pc_qty, 105 | Side::Ask, 106 | srm_msrm_discount, 107 | ) 108 | } 109 | 110 | // Executes the buy order portion of the swap, purchasing as much of the 111 | // base currency as possible, for the given `quote_amount`. 112 | // 113 | // `quote_amount` is the "native" amount of the quote currency, i.e., token 114 | // amount including decimals. 115 | pub fn buy( 116 | &self, 117 | quote_amount: u64, 118 | srm_msrm_discount: Option>, 119 | ) -> ProgramResult { 120 | let limit_price = u64::MAX; 121 | let max_coin_qty = u64::MAX; 122 | let max_native_pc_qty = quote_amount; 123 | self.order_cpi( 124 | limit_price, 125 | max_coin_qty, 126 | max_native_pc_qty, 127 | Side::Bid, 128 | srm_msrm_discount, 129 | ) 130 | } 131 | 132 | // Executes a new order on the serum dex via CPI. 133 | // 134 | // * `limit_price` - the limit order price in lot units. 135 | // * `max_coin_qty`- the max number of the base currency lot units. 136 | // * `max_native_pc_qty` - the max number of quote currency in native token 137 | // units (includes decimals). 138 | // * `side` - bid or ask, i.e. the type of order. 139 | // * `referral` - referral account, earning a fee. 140 | fn order_cpi( 141 | &self, 142 | limit_price: u64, 143 | max_coin_qty: u64, 144 | max_native_pc_qty: u64, 145 | side: Side, 146 | srm_msrm_discount: Option>, 147 | ) -> ProgramResult { 148 | // Client order id is only used for cancels. Not used here so hardcode. 149 | let client_order_id = 0; 150 | // Limit is the dex's custom compute budge parameter, setting an upper 151 | // bound on the number of matching cycles the program can perform 152 | // before giving up and posting the remaining unmatched order. 153 | let limit = 65535; 154 | 155 | // let srm_msrm_discount_key = match srm_msrm_discount { 156 | // Some(srm_msrm_discount) => Some(srm_msrm_discount.key), 157 | // None => None, 158 | // }; 159 | // let mut ctx = CpiContext::new(self.dex_program.clone(), self.clone().into()); 160 | // if let Some(srm_msrm_discount) = srm_msrm_discount { 161 | // ctx = ctx.with_remaining_accounts(vec![srm_msrm_discount]); 162 | // } 163 | let mut accounts = vec![ 164 | self.market.market.clone(), 165 | self.market.open_orders.clone(), 166 | self.market.request_queue.clone(), 167 | self.market.event_queue.clone(), 168 | self.market.bids.clone(), 169 | self.market.asks.clone(), 170 | self.market.order_payer_authority.clone(), 171 | self.open_order_authority.clone(), 172 | self.market.coin_vault.clone(), 173 | self.market.pc_vault.clone(), 174 | self.token_program.clone(), 175 | self.rent.clone(), 176 | ]; 177 | if let Some(account) = srm_msrm_discount.clone() { 178 | accounts.push(account) 179 | }; 180 | let srm_msrm_discount_key = match srm_msrm_discount { 181 | Some(acc) => { 182 | accounts.push(acc.clone()); 183 | Some(acc.key) 184 | } 185 | None => None, 186 | }; 187 | accounts.push(self.dex_program.clone()); 188 | 189 | let instruction = instruction::new_order( 190 | self.market.market.key, 191 | self.market.open_orders.key, 192 | self.market.request_queue.key, 193 | self.market.event_queue.key, 194 | self.market.bids.key, 195 | self.market.asks.key, 196 | self.market.order_payer_authority.key, 197 | self.open_order_authority.key, 198 | self.market.coin_vault.key, 199 | self.market.pc_vault.key, 200 | self.token_program.key, 201 | self.rent.key, 202 | srm_msrm_discount_key, 203 | self.dex_program.key, 204 | side, 205 | NonZeroU64::new(limit_price).unwrap(), 206 | NonZeroU64::new(max_coin_qty).unwrap(), 207 | OrderType::ImmediateOrCancel, 208 | client_order_id, 209 | SelfTradeBehavior::DecrementTake, 210 | limit, 211 | NonZeroU64::new(max_native_pc_qty).unwrap(), 212 | ) 213 | .map_err(|_| ProtocolError::InvalidDelegate)?; 214 | 215 | invoke(&instruction, &accounts[..])?; 216 | Ok(()) 217 | } 218 | 219 | pub fn settle(&self, referral: Option>) -> ProgramResult { 220 | let mut accounts = vec![ 221 | self.market.market.clone(), 222 | self.market.open_orders.clone(), 223 | self.open_order_authority.clone(), 224 | self.market.coin_vault.clone(), 225 | self.market.pc_vault.clone(), 226 | self.market.coin_wallet.clone(), 227 | self.pc_wallet.clone(), 228 | self.market.vault_signer.clone(), 229 | self.token_program.clone(), 230 | self.dex_program.clone(), 231 | ]; 232 | let referral_key = match referral { 233 | Some(referral_acc) => { 234 | accounts.push(referral_acc.clone()); 235 | Some(referral_acc.key) 236 | } 237 | None => None, 238 | }; 239 | let instruction = instruction::settle_funds( 240 | self.dex_program.key, 241 | self.market.market.key, 242 | self.token_program.key, 243 | self.market.open_orders.key, 244 | self.open_order_authority.key, 245 | self.market.coin_vault.key, 246 | self.market.coin_wallet.key, 247 | self.market.pc_vault.key, 248 | self.pc_wallet.key, 249 | referral_key, 250 | self.market.vault_signer.key, 251 | )?; 252 | invoke(&instruction, &accounts[..])?; 253 | Ok(()) 254 | } 255 | } 256 | 257 | // Returns the amount of lots for the base currency of a trade with `size`. 258 | fn coin_lots(market: &MarketState, size: u64) -> u64 { 259 | size.checked_div(market.coin_lot_size).unwrap() 260 | } 261 | 262 | #[allow(dead_code)] 263 | pub fn invoke_init_open_orders<'a>( 264 | base_seed: &[u8], 265 | program_id: &Pubkey, 266 | open_orders: &AccountInfo<'a>, 267 | authority: &AccountInfo<'a>, 268 | market: &AccountInfo<'a>, 269 | rent: &AccountInfo<'a>, 270 | nonce: u8, 271 | ) -> Result<(), ProtocolError> { 272 | let authority_signature_seeds = [base_seed, &[nonce]]; 273 | let signers = &[&authority_signature_seeds[..]]; 274 | 275 | let ix = init_open_orders(program_id, open_orders.key, authority.key, market.key, None) 276 | .map_err(|_| ProtocolError::InitOpenOrdersInstructionError)?; 277 | invoke_signed( 278 | &ix, 279 | &[ 280 | open_orders.clone(), 281 | authority.clone(), 282 | market.clone(), 283 | rent.clone(), 284 | ], 285 | signers, 286 | ) 287 | .map_err(|_| ProtocolError::InvokeError) 288 | } 289 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/serum_dex/state.rs: -------------------------------------------------------------------------------- 1 | use arrayref::{array_ref, array_refs}; 2 | use solana_program::{program_error::ProgramError, pubkey::Pubkey}; 3 | 4 | const ACCOUNT_HEAD_PADDING: &[u8; 5] = b"serum"; 5 | const ACCOUNT_TAIL_PADDING: &[u8; 7] = b"padding"; 6 | 7 | #[repr(C)] 8 | #[derive(Debug, PartialEq, Clone)] 9 | pub struct MarketState { 10 | // 0 11 | pub account_flags: u64, // Initialized, Market 12 | // 1 13 | pub own_address: Pubkey, 14 | // 5 15 | pub vault_signer_nonce: u64, 16 | // 6 17 | pub coin_mint: Pubkey, 18 | // 10 19 | pub pc_mint: Pubkey, 20 | 21 | // 14 22 | pub coin_vault: Pubkey, 23 | // 18 24 | pub coin_deposits_total: u64, 25 | // 19 26 | pub coin_fees_accrued: u64, 27 | 28 | // 20 29 | pub pc_vault: Pubkey, 30 | // 24 31 | pub pc_deposits_total: u64, 32 | // 25 33 | pub pc_fees_accrued: u64, 34 | 35 | // 26 36 | pub pc_dust_threshold: u64, 37 | 38 | // 27 39 | pub req_q: Pubkey, 40 | // 31 41 | pub event_q: Pubkey, 42 | 43 | // 35 44 | pub bids: Pubkey, 45 | // 39 46 | pub asks: Pubkey, 47 | 48 | // 43 49 | pub coin_lot_size: u64, 50 | // 44 51 | pub pc_lot_size: u64, 52 | // // 45 53 | // pub fee_rate_bps: u64, 54 | // // 46 55 | // pub referrer_rebates_accrued: u64, 56 | } 57 | 58 | impl MarketState { 59 | #[allow(dead_code)] 60 | const LEN: usize = 388; 61 | 62 | pub fn unpack_from_slice(input: &[u8]) -> Result { 63 | if input.len() <= 12 { 64 | return Err(ProgramError::InvalidAccountData); 65 | } 66 | #[allow(clippy::ptr_offset_with_cast)] 67 | let (head, data, tail) = array_refs![input, 5; ..; 7]; 68 | if head != ACCOUNT_HEAD_PADDING { 69 | return Err(ProgramError::InvalidAccountData); 70 | } 71 | if tail != ACCOUNT_TAIL_PADDING { 72 | return Err(ProgramError::InvalidAccountData); 73 | } 74 | let input = array_ref![data, 0, 360]; 75 | let ( 76 | account_flags_arr, 77 | own_address_arr, 78 | vault_signer_nonce, 79 | coin_mint_arr, 80 | pc_mint_arr, 81 | coin_vault_arr, 82 | coin_deposits_total_arr, 83 | coin_fees_accrued_arr, 84 | pc_vault_arr, 85 | pc_deposits_total_arr, 86 | pc_fees_accrued_arr, 87 | pc_dust_threshold_arr, 88 | req_q_arr, 89 | event_q_arr, 90 | bids_arr, 91 | asks_arr, 92 | coin_lot_size_arr, 93 | pc_lot_size_arr, 94 | ) = array_refs![input, 8, 32, 8, 32, 32, 32, 8, 8, 32, 8, 8, 8, 32, 32, 32, 32, 8, 8]; 95 | Ok(MarketState { 96 | account_flags: u64::from_le_bytes(*account_flags_arr), 97 | own_address: Pubkey::new_from_array(*own_address_arr), 98 | vault_signer_nonce: u64::from_le_bytes(*vault_signer_nonce), 99 | coin_mint: Pubkey::new_from_array(*coin_mint_arr), 100 | pc_mint: Pubkey::new_from_array(*pc_mint_arr), 101 | coin_vault: Pubkey::new_from_array(*coin_vault_arr), 102 | coin_deposits_total: u64::from_le_bytes(*coin_deposits_total_arr), 103 | coin_fees_accrued: u64::from_le_bytes(*coin_fees_accrued_arr), 104 | pc_vault: Pubkey::new_from_array(*pc_vault_arr), 105 | pc_deposits_total: u64::from_le_bytes(*pc_deposits_total_arr), 106 | pc_fees_accrued: u64::from_le_bytes(*pc_fees_accrued_arr), 107 | pc_dust_threshold: u64::from_le_bytes(*pc_dust_threshold_arr), 108 | req_q: Pubkey::new_from_array(*req_q_arr), 109 | event_q: Pubkey::new_from_array(*event_q_arr), 110 | bids: Pubkey::new_from_array(*bids_arr), 111 | asks: Pubkey::new_from_array(*asks_arr), 112 | coin_lot_size: u64::from_le_bytes(*coin_lot_size_arr), 113 | pc_lot_size: u64::from_le_bytes(*pc_lot_size_arr), 114 | }) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/spl_token_swap/instruction.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | instruction::{AccountMeta, Instruction}, 3 | program_error::ProgramError, 4 | pubkey::Pubkey, 5 | }; 6 | use std::mem::size_of; 7 | 8 | /// Swap instruction data 9 | #[repr(C)] 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub struct Swap { 12 | /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 13 | pub amount_in: u64, 14 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 15 | pub minimum_amount_out: u64, 16 | } 17 | 18 | /// Instructions supported by the token swap program. 19 | #[repr(C)] 20 | #[derive(Debug, PartialEq)] 21 | pub enum SwapInstruction { 22 | /// Swap the tokens in the pool. 23 | /// 24 | /// 0. `[]` Token-swap 25 | /// 1. `[]` swap authority 26 | /// 2. `[]` user transfer authority 27 | /// 3. `[writable]` token_(A|B) SOURCE Account, amount is transferable by user transfer authority, 28 | /// 4. `[writable]` token_(A|B) Base Account to swap INTO. Must be the SOURCE token. 29 | /// 5. `[writable]` token_(A|B) Base Account to swap FROM. Must be the DESTINATION token. 30 | /// 6. `[writable]` token_(A|B) DESTINATION Account assigned to USER as the owner. 31 | /// 7. `[writable]` Pool token mint, to generate trading fees 32 | /// 8. `[writable]` Fee account, to receive trading fees 33 | /// 9. '[]` Token program id 34 | /// 10 `[optional, writable]` Host fee account to receive additional trading fees 35 | Swap(Swap), 36 | } 37 | 38 | impl SwapInstruction { 39 | /// Packs a [SwapInstruction](enum.SwapInstruction.html) into a byte buffer. 40 | pub fn pack(&self) -> Vec { 41 | let mut buf = Vec::with_capacity(size_of::()); 42 | match &*self { 43 | Self::Swap(Swap { 44 | amount_in, 45 | minimum_amount_out, 46 | }) => { 47 | buf.push(1); 48 | buf.extend_from_slice(&amount_in.to_le_bytes()); 49 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 50 | } 51 | } 52 | buf 53 | } 54 | } 55 | 56 | /// Creates a 'swap' instruction. 57 | #[allow(clippy::too_many_arguments)] 58 | pub fn swap( 59 | program_id: &Pubkey, 60 | token_program_id: &Pubkey, 61 | swap_pubkey: &Pubkey, 62 | authority_pubkey: &Pubkey, 63 | user_transfer_authority_pubkey: &Pubkey, 64 | source_pubkey: &Pubkey, 65 | swap_source_pubkey: &Pubkey, 66 | swap_destination_pubkey: &Pubkey, 67 | destination_pubkey: &Pubkey, 68 | pool_mint_pubkey: &Pubkey, 69 | pool_fee_pubkey: &Pubkey, 70 | host_fee_pubkey: Option<&Pubkey>, 71 | instruction: Swap, 72 | ) -> Result { 73 | let data = SwapInstruction::Swap(instruction).pack(); 74 | 75 | let mut accounts = vec![ 76 | AccountMeta::new_readonly(*swap_pubkey, false), 77 | AccountMeta::new_readonly(*authority_pubkey, false), 78 | AccountMeta::new_readonly(*user_transfer_authority_pubkey, true), 79 | AccountMeta::new(*source_pubkey, false), 80 | AccountMeta::new(*swap_source_pubkey, false), 81 | AccountMeta::new(*swap_destination_pubkey, false), 82 | AccountMeta::new(*destination_pubkey, false), 83 | AccountMeta::new(*pool_mint_pubkey, false), 84 | AccountMeta::new(*pool_fee_pubkey, false), 85 | AccountMeta::new_readonly(*token_program_id, false), 86 | ]; 87 | if let Some(host_fee_pubkey) = host_fee_pubkey { 88 | accounts.push(AccountMeta::new(*host_fee_pubkey, false)); 89 | } 90 | 91 | Ok(Instruction { 92 | program_id: *program_id, 93 | accounts, 94 | data, 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/spl_token_swap/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/stable_swap/instruction.rs: -------------------------------------------------------------------------------- 1 | //! Instruction types 2 | 3 | #![allow(clippy::too_many_arguments)] 4 | 5 | use std::mem::size_of; 6 | 7 | use solana_program::{ 8 | instruction::{AccountMeta, Instruction}, 9 | program_error::ProgramError, 10 | pubkey::Pubkey, 11 | }; 12 | 13 | /// Swap instruction data 14 | #[repr(C)] 15 | #[derive(Clone, Copy, Debug, PartialEq)] 16 | #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] 17 | pub struct SwapData { 18 | /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate 19 | pub amount_in: u64, 20 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 21 | pub minimum_amount_out: u64, 22 | } 23 | 24 | /// Instructions supported by the SwapInfo program. 25 | #[repr(C)] 26 | #[derive(Copy, Clone, Debug, PartialEq)] 27 | #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] 28 | pub enum SwapInstruction { 29 | /// Swap the tokens in the pool. 30 | /// 31 | /// 0. `[]`StableSwap 32 | /// 1. `[]` $authority 33 | /// 2. `[signer]` User authority. 34 | /// 3. `[writable]` token_(A|B) SOURCE Account, amount is transferable by $authority, 35 | /// 4. `[writable]` token_(A|B) Base Account to swap INTO. Must be the SOURCE token. 36 | /// 5. `[writable]` token_(A|B) Base Account to swap FROM. Must be the DESTINATION token. 37 | /// 6. `[writable]` token_(A|B) DESTINATION Account assigned to USER as the owner. 38 | /// 7. `[writable]` token_(A|B) admin fee Account. Must have same mint as DESTINATION token. 39 | /// 8. `[]` Token program id 40 | Swap(SwapData), 41 | } 42 | 43 | impl SwapInstruction { 44 | /// Packs a [SwapInstruction](enum.SwapInstruction.html) into a byte buffer. 45 | pub fn pack(&self) -> Vec { 46 | let mut buf = Vec::with_capacity(size_of::()); 47 | match *self { 48 | Self::Swap(SwapData { 49 | amount_in, 50 | minimum_amount_out, 51 | }) => { 52 | buf.push(1); 53 | buf.extend_from_slice(&amount_in.to_le_bytes()); 54 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 55 | } 56 | } 57 | buf 58 | } 59 | } 60 | 61 | /// Creates a 'swap' instruction. 62 | #[inline(always)] 63 | pub fn swap( 64 | program_id: &Pubkey, 65 | token_program_id: &Pubkey, 66 | swap_pubkey: &Pubkey, 67 | swap_authority_key: &Pubkey, 68 | user_authority_key: &Pubkey, 69 | source_pubkey: &Pubkey, 70 | swap_source_pubkey: &Pubkey, 71 | swap_destination_pubkey: &Pubkey, 72 | destination_pubkey: &Pubkey, 73 | admin_fee_destination_pubkey: &Pubkey, 74 | amount_in: u64, 75 | minimum_amount_out: u64, 76 | ) -> Result { 77 | let data = SwapInstruction::Swap(SwapData { 78 | amount_in, 79 | minimum_amount_out, 80 | }) 81 | .pack(); 82 | 83 | let accounts = vec![ 84 | AccountMeta::new_readonly(*swap_pubkey, false), 85 | AccountMeta::new_readonly(*swap_authority_key, false), 86 | AccountMeta::new_readonly(*user_authority_key, true), 87 | AccountMeta::new(*source_pubkey, false), 88 | AccountMeta::new(*swap_source_pubkey, false), 89 | AccountMeta::new(*swap_destination_pubkey, false), 90 | AccountMeta::new(*destination_pubkey, false), 91 | AccountMeta::new(*admin_fee_destination_pubkey, false), 92 | AccountMeta::new_readonly(*token_program_id, false), 93 | ]; 94 | 95 | Ok(Instruction { 96 | program_id: *program_id, 97 | accounts, 98 | data, 99 | }) 100 | } 101 | -------------------------------------------------------------------------------- /src/program-rust/src/exchanger/stable_swap/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | -------------------------------------------------------------------------------- /src/program-rust/src/instruction.rs: -------------------------------------------------------------------------------- 1 | //! Instruction types 2 | 3 | use crate::error::ProtocolError; 4 | use arrayref::{array_ref, array_refs}; 5 | use solana_program::program_error::ProgramError; 6 | use std::num::NonZeroU64; 7 | 8 | /// ExchangerType 9 | #[derive(Clone, Debug, PartialEq, Copy)] 10 | pub enum ExchangerType { 11 | /// ExchangerType SplTokenSwap 12 | SplTokenSwap, 13 | /// ExchangerType SerumDex 14 | SerumDex, 15 | /// Saber StableSwap 16 | StableSwap, 17 | /// Raydium swap 18 | RaydiumSwap, 19 | /// Raydium swap slim 20 | RaydiumSwapSlim, 21 | /// CremaFinance 22 | CremaFinance, 23 | /// AldrinExchange 24 | AldrinExchange, 25 | /// CropperFinance 26 | CropperFinance, 27 | } 28 | 29 | impl ExchangerType { 30 | pub fn from(value: u8) -> Option { 31 | match value { 32 | 0 => Some(ExchangerType::SplTokenSwap), 33 | 1 => Some(ExchangerType::SerumDex), 34 | 2 => Some(ExchangerType::StableSwap), 35 | 3 => Some(ExchangerType::RaydiumSwap), 36 | 4 => Some(ExchangerType::RaydiumSwapSlim), 37 | 5 => Some(ExchangerType::CremaFinance), 38 | 6 => Some(ExchangerType::AldrinExchange), 39 | 7 => Some(ExchangerType::CropperFinance), 40 | _ => None, 41 | } 42 | } 43 | } 44 | 45 | /// Initialize instruction data 46 | #[derive(Clone, Debug, PartialEq)] 47 | pub struct Initialize { 48 | /// nonce used to create validate program address 49 | pub nonce: u8, 50 | } 51 | 52 | /// Swap instruction data 53 | #[derive(Clone, Debug, PartialEq)] 54 | pub struct SwapInstruction { 55 | /// amount of tokens to swap 56 | pub amount_in: NonZeroU64, 57 | /// expect amount of tokens to swap 58 | pub expect_amount_out: NonZeroU64, 59 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 60 | pub minimum_amount_out: NonZeroU64, 61 | } 62 | 63 | /// Swap instruction data 64 | #[derive(Clone, Debug, PartialEq, Eq)] 65 | pub struct SwapInInstruction { 66 | /// amount of tokens to swap 67 | pub amount_in: NonZeroU64, 68 | } 69 | 70 | /// Swap instruction data 71 | #[derive(Clone, Debug, PartialEq, Eq)] 72 | pub struct SwapOutInstruction { 73 | /// expect amount of tokens to swap 74 | pub expect_amount_out: NonZeroU64, 75 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 76 | pub minimum_amount_out: NonZeroU64, 77 | } 78 | 79 | /// Swap instruction data 80 | #[derive(Clone, Debug, PartialEq, Eq)] 81 | pub struct SwapOutSlimInstruction { 82 | /// Minimum amount of DESTINATION token to output, prevents excessive slippage 83 | pub minimum_amount_out: NonZeroU64, 84 | } 85 | 86 | // Instructions supported by the 1sol protocol program 87 | #[repr(C)] 88 | #[derive(Debug, PartialEq)] 89 | pub enum ProtocolInstruction { 90 | /// Swap the tokens in the pool. 91 | /// 92 | /// user accounts 93 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 94 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 95 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 96 | /// 3. '[]` Token program id 97 | /// 4. `[writable]` fee token account 98 | /// 5. `[]` TokenSwap swap_info account 99 | /// 6. `[]` TokenSwap swap_info authority 100 | /// 7. `[writable]` TokenSwap token_A Account. 101 | /// 8. `[writable]` TokenSwap token_B Account. 102 | /// 9. `[writable]` TokenSwap Pool token mint, to generate trading fees 103 | /// 10. `[writable]` TokenSwap Fee account, to receive trading fees 104 | /// 11. '[]` Token-Swap program id 105 | /// 12. `[optional, writable]` Host fee account to receive additional trading fees 106 | SwapSplTokenSwap(SwapInstruction), 107 | 108 | /// Swap the tokens in the serum dex market. 109 | /// 110 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 111 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 112 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 113 | /// 3. '[]` Token program id 114 | /// 4. `[writable]` fee token account 115 | /// 5. `[writable]` user market open_orders 116 | /// 6. `[writable]` serum-dex market 117 | /// 7. `[writable]` serum-dex request_queue 118 | /// 8. `[writable]` serum-dex event_queue 119 | /// 9. `[writable]` serum-dex market_bids 120 | /// 10. `[writable]` serum-dex market_asks 121 | /// 11. `[writable]` serum-dex coin_vault 122 | /// 12. `[writable]` serum-dex pc_vault 123 | /// 13. `[]` serum-dex vault_signer for settleFunds 124 | /// 14. `[]` serum-dex rent_sysvar 125 | /// 15. `[]` serum-dex serum_dex_program_id 126 | SwapSerumDex(SwapInstruction), 127 | 128 | /// Swap tokens through Saber StableSwap 129 | /// 130 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 131 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 132 | /// 2. `[-signer]` User token SOURCE account OWNER (or Authority) account. 133 | /// 3. '[]` Token program id. 134 | /// 4. `[writable]` fee token account. 135 | /// 6. `[]` StableSwap info. 136 | /// 7. `[]` StableSwap authority. 137 | /// 8. `[writable]` StableSwap token a account. 138 | /// 9. `[writable]` StableSwap token b account. 139 | /// 10. `[writable]` StableSwap admin fee account. Must have same mint as User DESTINATION token account. 140 | /// 11. `[]` StableSwap clock id. 141 | /// 12. `[]` StableSwap program id. 142 | SwapStableSwap(SwapInstruction), 143 | 144 | /// Swap tokens through Raydium-Swap 145 | /// 146 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 147 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 148 | /// 2. `[-signer]` User token SOURCE account OWNER (or Authority) account. 149 | /// 3. '[]` Token program id. 150 | /// 4. `[writable]` fee token account. 151 | /// 6. `[writable]` raydium amm account. 152 | /// 7. `[]` raydium $authority. 153 | /// 8. `[writable]` raydium open_orders account. 154 | /// 9. `[writable]` raydium target_orders account. 155 | /// 10. `[writable]` raydium pool_token_coin account. 156 | /// 11. `[writable]` raydium pool_token_pc account. 157 | /// 12. `[]` serum-dex program id. 158 | /// 13. `[writable]` raydium serum_market account. 159 | /// 14. `[writable]` raydium bids account. 160 | /// 15. `[writable]` raydium asks account. 161 | /// 16. `[writable]` raydium event_q account. 162 | /// 17. `[writable]` raydium coin_vault account. 163 | /// 18. `[writable]` raydium pc_vault account. 164 | /// 19. `[]` raydium vault_signer account. 165 | /// 20. `[]` raydium program id. 166 | SwapRaydiumSwap(SwapInstruction), 167 | 168 | /// Initialize a new swap info account 169 | /// 1. `[writable, signer]` The swapInfo account for initializing 170 | /// 2. `[signer]` User account 171 | InitializeSwapInfo, 172 | 173 | /// Setup SwapInfo account 174 | /// 1. `[writable]` The swapInfo account for setup 175 | /// 2. `[]` TokenAccount to set 176 | SetupSwapInfo, 177 | 178 | /// Close SwapInfo account 179 | /// 1. `[writable]` The swapInfo account for close 180 | /// 2. `[signer]` owner account 181 | /// 3. `[writable]` destination account 182 | CloseSwapInfo, 183 | 184 | /// Swap the tokens in the pool. 185 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 186 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 187 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 188 | /// 3. '[writable]` SwapInfo account 189 | /// 4. '[]` Token program id 190 | /// 5. `[]` TokenSwap swap_info account 191 | /// 6. `[]` TokenSwap swap_info authority 192 | /// 7. `[writable]` TokenSwap token_A Account. 193 | /// 8. `[writable]` TokenSwap token_B Account. 194 | /// 9. `[writable]` TokenSwap Pool token mint, to generate trading fees 195 | /// 10. `[writable]` TokenSwap Fee account, to receive trading fees 196 | /// 11. '[]` Token-Swap program id 197 | SwapSplTokenSwapIn(SwapInInstruction), 198 | 199 | /// Swap the tokens in the serum dex market. 200 | /// 201 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 202 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 203 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 204 | /// 3. '[writable]` SwapInfo account 205 | /// 4. '[]` Token program id 206 | /// 5. `[writable]` user market open_orders 207 | /// 6. `[writable]` serum-dex market 208 | /// 7. `[writable]` serum-dex request_queue 209 | /// 8. `[writable]` serum-dex event_queue 210 | /// 9. `[writable]` serum-dex market_bids 211 | /// 10. `[writable]` serum-dex market_asks 212 | /// 11. `[writable]` serum-dex coin_vault 213 | /// 12. `[writable]` serum-dex pc_vault 214 | /// 13. `[]` serum-dex vault_signer for settleFunds 215 | /// 14. `[]` serum-dex rent_sysvar 216 | /// 15. `[]` serum-dex serum_dex_program_id 217 | SwapSerumDexIn(SwapInInstruction), 218 | 219 | /// Swap tokens through Saber StableSwap 220 | /// 221 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 222 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 223 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 224 | /// 3. '[writable]` SwapInfo account 225 | /// 4. '[]` Token program id. 226 | /// 5. `[]` StableSwap info. 227 | /// 6. `[]` StableSwap authority. 228 | /// 7. `[writable]` StableSwap token a account. 229 | /// 8. `[writable]` StableSwap token b account. 230 | /// 9. `[writable]` StableSwap admin fee account. Must have same mint as User DESTINATION token account. 231 | /// 10. `[]` StableSwap clock id. 232 | /// 11. `[]` StableSwap program id. 233 | SwapStableSwapIn(SwapInInstruction), 234 | 235 | /// Swap tokens through Raydium-Swap 236 | /// 237 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 238 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 239 | /// 2. `[signer]` User token0 SOURCE account OWNER (or Authority) account. 240 | /// 3. '[writable]` SwapInfo account 241 | /// 4. '[]` Token program id. 242 | /// 5. `[writable]` raydium amm account. 243 | /// 6. `[]` raydium $authority. 244 | /// 7. `[writable]` raydium open_orders account. 245 | /// 8. `[writable]` raydium target_orders account. 246 | /// 9. `[writable]` raydium pool_token_coin account. 247 | /// 10. `[writable]` raydium pool_token_pc account. 248 | /// 11. `[]` serum-dex program id. 249 | /// 12. `[writable]` raydium serum_market account. 250 | /// 13. `[writable]` raydium bids account. 251 | /// 14. `[writable]` raydium asks account. 252 | /// 15. `[writable]` raydium event_q account. 253 | /// 16. `[writable]` raydium coin_vault account. 254 | /// 17. `[writable]` raydium pc_vault account. 255 | /// 18. `[]` raydium vault_signer account. 256 | /// 19. `[]` raydium program id. 257 | SwapRaydiumIn(SwapInInstruction), 258 | 259 | /// Swap the tokens in the pool. 260 | /// 261 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 262 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 263 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 264 | /// 3. '[writable]` SwapInfo account 265 | /// 4. '[]` Token program id 266 | /// 5. `[writable]` fee token account 267 | /// 6. `[]` TokenSwap swap_info account 268 | /// 7. `[]` TokenSwap swap_info authority 269 | /// 8. `[writable]` TokenSwap token_A Account. 270 | /// 9. `[writable]` TokenSwap token_B Account. 271 | /// 10. `[writable]` TokenSwap Pool token mint, to generate trading fees 272 | /// 11. `[writable]` TokenSwap Fee account, to receive trading fees 273 | /// 12. '[]` Token-Swap program id 274 | SwapSplTokenSwapOut(SwapOutInstruction), 275 | 276 | /// Swap the tokens in the serum dex market. 277 | /// 278 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 279 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 280 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 281 | /// 3. '[writable]` SwapInfo account 282 | /// 4. '[]` Token program id 283 | /// 5. `[writable]` fee token account 284 | /// 6. `[writable]` user market open_orders 285 | /// 7. `[writable]` serum-dex market 286 | /// 8. `[writable]` serum-dex request_queue 287 | /// 9. `[writable]` serum-dex event_queue 288 | /// 10. `[writable]` serum-dex market_bids 289 | /// 11. `[writable]` serum-dex market_asks 290 | /// 12. `[writable]` serum-dex coin_vault 291 | /// 13. `[writable]` serum-dex pc_vault 292 | /// 14. `[]` serum-dex vault_signer for settleFunds 293 | /// 15. `[]` serum-dex rent_sysvar 294 | /// 16. `[]` serum-dex serum_dex_program_id 295 | SwapSerumDexOut(SwapOutInstruction), 296 | 297 | /// Swap tokens through Saber StableSwap 298 | /// 299 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 300 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 301 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 302 | /// 3. '[writable]` SwapInfo account 303 | /// 4. '[]` Token program id. 304 | /// 5. `[writable]` fee token account. 305 | /// 6. `[]` StableSwap info. 306 | /// 7. `[]` StableSwap authority. 307 | /// 8. `[writable]` StableSwap token a account. 308 | /// 9. `[writable]` StableSwap token b account. 309 | /// 10. `[writable]` StableSwap admin fee account. Must have same mint as User DESTINATION token account. 310 | /// 11. `[]` StableSwap clock id. 311 | /// 12. `[]` StableSwap program id. 312 | SwapStableSwapOut(SwapOutInstruction), 313 | 314 | /// Swap tokens through Raydium-Swap 315 | /// 316 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 317 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 318 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 319 | /// 3. '[writable]` SwapInfo account 320 | /// 4. '[]` Token program id. 321 | /// 5. `[writable]` fee token account. 322 | /// 6. `[writable]` raydium amm account. 323 | /// 7. `[]` raydium $authority. 324 | /// 8. `[writable]` raydium open_orders account. 325 | /// 9. `[writable]` raydium target_orders account. 326 | /// 10. `[writable]` raydium pool_token_coin account. 327 | /// 11. `[writable]` raydium pool_token_pc account. 328 | /// 12. `[]` serum-dex program id. 329 | /// 13. `[writable]` raydium serum_market account. 330 | /// 14. `[writable]` raydium bids account. 331 | /// 15. `[writable]` raydium asks account. 332 | /// 16. `[writable]` raydium event_q account. 333 | /// 17. `[writable]` raydium coin_vault account. 334 | /// 18. `[writable]` raydium pc_vault account. 335 | /// 10. `[]` raydium vault_signer account. 336 | /// 20. `[]` raydium program id. 337 | SwapRaydiumOut(SwapOutInstruction), 338 | 339 | /// Swap tokens through Raydium-Swap 340 | /// 341 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 342 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 343 | /// 2. `[signer]` User token0 SOURCE account OWNER (or Authority) account. 344 | /// 3. '[writable]` SwapInfo account 345 | /// 4. '[]` Token program id. 346 | /// 5. `[writable]` raydium amm account. 347 | /// 6. `[]` raydium $authority. 348 | /// 7. `[writable]` raydium open_orders account. 349 | /// 8. `[writable]` raydium pool_token_coin account. 350 | /// 9. `[writable]` raydium pool_token_pc account. 351 | /// 10. `[]` serum-dex program id. 352 | /// 11. `[writable]` raydium serum_market account. 353 | /// 12. `[writable]` raydium bids account. 354 | /// 13. `[writable]` raydium asks account. 355 | /// 14. `[writable]` raydium event_q account. 356 | /// 15. `[writable]` raydium coin_vault account. 357 | /// 16. `[writable]` raydium pc_vault account. 358 | /// 17. `[]` raydium vault_signer account. 359 | /// 18. `[]` raydium program id. 360 | SwapRaydiumIn2(SwapInInstruction), 361 | 362 | /// Swap tokens through Raydium-Swap 363 | /// 364 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 365 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 366 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 367 | /// 3. '[writable]` SwapInfo account 368 | /// 4. '[]` Token program id. 369 | /// 5. `[writable]` fee token account. 370 | /// 6. `[writable]` raydium amm account. 371 | /// 7. `[]` raydium $authority. 372 | /// 8. `[writable]` raydium open_orders account. 373 | /// 9. `[writable]` raydium pool_token_coin account. 374 | /// 10. `[writable]` raydium pool_token_pc account. 375 | /// 11. `[]` serum-dex program id. 376 | /// 12. `[writable]` raydium serum_market account. 377 | /// 13. `[writable]` raydium bids account. 378 | /// 14. `[writable]` raydium asks account. 379 | /// 15. `[writable]` raydium event_q account. 380 | /// 16. `[writable]` raydium coin_vault account. 381 | /// 17. `[writable]` raydium pc_vault account. 382 | /// 18. `[]` raydium vault_signer account. 383 | /// 19. `[]` raydium program id. 384 | SwapRaydiumOut2(SwapOutSlimInstruction), 385 | 386 | /// Swap direct by CremaFinance 387 | /// 388 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 389 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 390 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 391 | /// 3. '[]` Token program id 392 | /// 4. `[writable]` fee token account 393 | /// 394 | /// 5. `[writable]` CremaFinance swap_info account 395 | /// 6. `[]` CremaFinance authority 396 | /// 7. `[writable]` CremaFinance token_A Account. 397 | /// 8. `[writable]` CremaFinance token_B Account. 398 | /// 9. `[writable]` CremaFinance tick dst Account. 399 | /// 10. '[]` CremaFinance program id 400 | SwapCremaFinance(SwapInstruction), 401 | 402 | /// SwapIn by CremaFinance 403 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 404 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 405 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 406 | /// 3. '[writable]` Protocol SwapInfo account 407 | /// 4. '[]` Token program id. 408 | /// 409 | /// 5. `[writable]` CremaFinance swap_info account 410 | /// 6. `[]` CremaFinance authority 411 | /// 7. `[writable]` CremaFinance token_A Account. 412 | /// 8. `[writable]` CremaFinance token_B Account. 413 | /// 9. `[writable]` CremaFinance tick dst Account. 414 | /// 10. '[]` CremaFinance program id 415 | SwapCremaFinanceIn(SwapInInstruction), 416 | 417 | /// SwapOut by CremaFinance 418 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 419 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 420 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 421 | /// 3. '[writable]` SwapInfo account 422 | /// 4. '[]` Token program id. 423 | /// 5. `[writable]` fee token account. 424 | /// 425 | /// 6. `[writable]` CremaFinance swap_info account 426 | /// 7. `[]` CremaFinance authority 427 | /// 8. `[writable]` CremaFinance token_A Account. 428 | /// 9. `[writable]` CremaFinance token_B Account. 429 | /// 10. `[writable]` CremaFinance tick dst Account. 430 | /// 11. '[]` CremaFinance program id 431 | SwapCremaFinanceOut(SwapOutInstruction), 432 | 433 | /// Swap direct by AldrinExchange 434 | /// 435 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 436 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 437 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 438 | /// 3. '[]` Token program id 439 | /// 4. `[writable]` fee token account 440 | /// 441 | /// 5. `[]` AldrinExchange pool_info account. 442 | /// 6. `[]` AldrinExchange pool authority. 443 | /// 7. `[writable]` AldrinExchange pool mint account. 444 | /// 8. `[writable]` AldrinExchange pool coin vault account. 445 | /// 9. `[writable]` AldrinExchange pool pc vault account. 446 | /// 10. `[writable]` AldrinExchange Pool fee account. 447 | /// 11. `[]` AldrinExchange Pool curve_key account. 448 | /// 12. '[]` AldrinExchange program id. 449 | SwapAldrinExchange(SwapInstruction), 450 | 451 | /// SwapIn by AldrinExchange 452 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 453 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 454 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 455 | /// 3. '[writable]` Protocol SwapInfo account 456 | /// 4. '[]` Token program id. 457 | /// 458 | /// 5. `[]` AldrinExchange pool_info account. 459 | /// 6. `[]` AldrinExchange pool authority. 460 | /// 7. `[writable]` AldrinExchange pool mint account. 461 | /// 8. `[writable]` AldrinExchange pool coin vault account. 462 | /// 9. `[writable]` AldrinExchange pool pc vault account. 463 | /// 10. `[writable]` AldrinExchange Pool fee account. 464 | /// 11. `[]` AldrinExchange Pool curve_key account. 465 | /// 12. '[]` AldrinExchange program id. 466 | SwapAldrinExchangeIn(SwapInInstruction), 467 | 468 | /// SwapOut by AldrinExchange 469 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 470 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 471 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 472 | /// 3. '[writable]` SwapInfo account 473 | /// 4. '[]` Token program id. 474 | /// 5. `[writable]` fee token account. 475 | /// 476 | /// 6. `[]` AldrinExchange pool_info account. 477 | /// 7. `[]` AldrinExchange pool authority. 478 | /// 8. `[writable]` AldrinExchange pool mint account. 479 | /// 9. `[writable]` AldrinExchange pool coin vault account. 480 | /// 10. `[writable]` AldrinExchange pool pc vault account. 481 | /// 11. `[writable]` AldrinExchange Pool fee account. 482 | /// 12. `[]` AldrinExchange Pool curve_key account. 483 | /// 13. '[]` AldrinExchange program id. 484 | SwapAldrinExchangeOut(SwapOutInstruction), 485 | 486 | /// Swap direct by CropperFinance 487 | /// 488 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet) 489 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 490 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 491 | /// 3. '[]` Token program id 492 | /// 4. `[writable]` fee token account 493 | /// 494 | /// 5. `[]` CropperFinance swap_info account. 495 | /// 6. `[]` CropperFinance pool authority. 496 | /// 7. `[]` CropperFinance program state [3hsU1VgsBgBgz5jWiqdw9RfGU6TpWdCmdah1oi4kF3Tq]. 497 | /// 8. `[writable]` AldrinExchange pool token_a account. 498 | /// 9. `[writable]` AldrinExchange pool token_b account. 499 | /// 10. `[writable]` AldrinExchange pool mint account. 500 | /// 11. `[writable]` AldrinExchange Pool fee account. 501 | /// 12. '[]` AldrinExchange program id. 502 | SwapCropperFinance(SwapInstruction), 503 | 504 | /// SwapIn by CropperFinance 505 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 506 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 507 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 508 | /// 3. '[writable]` Protocol SwapInfo account 509 | /// 4. '[]` Token program id. 510 | /// 511 | /// 5. `[]` CropperFinance swap_info account. 512 | /// 6. `[]` CropperFinance pool authority. 513 | /// 7. `[]` CropperFinance program state [3hsU1VgsBgBgz5jWiqdw9RfGU6TpWdCmdah1oi4kF3Tq]. 514 | /// 8. `[writable]` AldrinExchange pool token_a account. 515 | /// 9. `[writable]` AldrinExchange pool token_b account. 516 | /// 10. `[writable]` AldrinExchange pool mint account. 517 | /// 11. `[writable]` AldrinExchange Pool fee account. 518 | /// 12. '[]` AldrinExchange program id. 519 | SwapCropperFinanceIn(SwapInInstruction), 520 | 521 | /// SwapOut by CropperFinance 522 | /// 0. `[writable]` User token SOURCE Account, (coin_wallet). 523 | /// 1. `[writable]` User token DESTINATION Account to swap INTO. Must be the DESTINATION token. 524 | /// 2. `[signer]` User token SOURCE account OWNER (or Authority) account. 525 | /// 3. '[writable]` SwapInfo account 526 | /// 4. '[]` Token program id. 527 | /// 5. `[writable]` fee token account. 528 | /// 529 | /// 6. `[]` CropperFinance swap_info account. 530 | /// 7. `[]` CropperFinance pool authority. 531 | /// 8. `[]` CropperFinance program state [3hsU1VgsBgBgz5jWiqdw9RfGU6TpWdCmdah1oi4kF3Tq]. 532 | /// 9. `[writable]` AldrinExchange pool token_a account. 533 | /// 10. `[writable]` AldrinExchange pool token_b account. 534 | /// 11. `[writable]` AldrinExchange pool mint account. 535 | /// 12. `[writable]` AldrinExchange Pool fee account. 536 | /// 13. '[]` AldrinExchange program id. 537 | SwapCropperFinanceOut(SwapOutInstruction), 538 | } 539 | 540 | impl ProtocolInstruction { 541 | /// Unpacks a byte buffer into a [OneSolInstruction](enum.OneSolInstruction.html). 542 | pub fn unpack(input: &[u8]) -> Result { 543 | let (&tag, rest) = input.split_first().ok_or(ProtocolError::InvalidInput)?; 544 | Ok(match tag { 545 | 3 => Self::SwapSplTokenSwap(SwapInstruction::unpack(rest)?), 546 | 4 => Self::SwapSerumDex(SwapInstruction::unpack(rest)?), 547 | 5 => return Err(ProtocolError::InvalidInstruction.into()), 548 | 6 => Self::SwapStableSwap(SwapInstruction::unpack(rest)?), 549 | 8 => return Err(ProtocolError::InvalidInstruction.into()), 550 | 9 => Self::SwapRaydiumSwap(SwapInstruction::unpack(rest)?), 551 | 10 => Self::InitializeSwapInfo, 552 | 11 => Self::SetupSwapInfo, 553 | 12 => Self::SwapSplTokenSwapIn(SwapInInstruction::unpack(rest)?), 554 | 13 => Self::SwapSplTokenSwapOut(SwapOutInstruction::unpack(rest)?), 555 | 14 => Self::SwapSerumDexIn(SwapInInstruction::unpack(rest)?), 556 | 15 => Self::SwapSerumDexOut(SwapOutInstruction::unpack(rest)?), 557 | 16 => Self::SwapStableSwapIn(SwapInInstruction::unpack(rest)?), 558 | 17 => Self::SwapStableSwapOut(SwapOutInstruction::unpack(rest)?), 559 | 18 => Self::SwapRaydiumIn(SwapInInstruction::unpack(rest)?), 560 | 19 => Self::SwapRaydiumOut(SwapOutInstruction::unpack(rest)?), 561 | 20 => Self::SwapRaydiumIn2(SwapInInstruction::unpack(rest)?), 562 | 21 => Self::SwapRaydiumOut2(SwapOutSlimInstruction::unpack(rest)?), 563 | 22 => Self::SwapCremaFinance(SwapInstruction::unpack(rest)?), 564 | 23 => Self::SwapCremaFinanceIn(SwapInInstruction::unpack(rest)?), 565 | 24 => Self::SwapCremaFinanceOut(SwapOutInstruction::unpack(rest)?), 566 | 25 => Self::SwapAldrinExchange(SwapInstruction::unpack(rest)?), 567 | 26 => Self::SwapAldrinExchangeIn(SwapInInstruction::unpack(rest)?), 568 | 27 => Self::SwapAldrinExchangeOut(SwapOutInstruction::unpack(rest)?), 569 | 28 => Self::SwapCropperFinance(SwapInstruction::unpack(rest)?), 570 | 29 => Self::SwapCropperFinanceIn(SwapInInstruction::unpack(rest)?), 571 | 30 => Self::SwapCropperFinanceOut(SwapOutInstruction::unpack(rest)?), 572 | 31 => Self::CloseSwapInfo, 573 | _ => return Err(ProtocolError::InvalidInstruction.into()), 574 | }) 575 | } 576 | } 577 | 578 | impl SwapInstruction { 579 | const DATA_LEN: usize = 24; 580 | 581 | // size = 1 or 3 582 | // flag[0/1], [account_size], [amount_in], [minium_amount_out] 583 | fn unpack(input: &[u8]) -> Result { 584 | if input.len() < SwapInstruction::DATA_LEN { 585 | return Err(ProtocolError::InvalidInput.into()); 586 | } 587 | let arr_data = array_ref![input, 0, SwapInstruction::DATA_LEN]; 588 | let (&amount_in_arr, &expect_amount_out_arr, &minimum_amount_out_arr) = 589 | array_refs![arr_data, 8, 8, 8]; 590 | let amount_in = 591 | NonZeroU64::new(u64::from_le_bytes(amount_in_arr)).ok_or(ProtocolError::InvalidInput)?; 592 | let expect_amount_out = NonZeroU64::new(u64::from_le_bytes(expect_amount_out_arr)) 593 | .ok_or(ProtocolError::InvalidInput)?; 594 | let minimum_amount_out = NonZeroU64::new(u64::from_le_bytes(minimum_amount_out_arr)) 595 | .ok_or(ProtocolError::InvalidInput)?; 596 | if expect_amount_out.get() < minimum_amount_out.get() || expect_amount_out.get() == 0 { 597 | return Err(ProtocolError::InvalidExpectAmountOut.into()); 598 | } 599 | Ok(SwapInstruction { 600 | amount_in, 601 | expect_amount_out, 602 | minimum_amount_out, 603 | }) 604 | } 605 | } 606 | 607 | impl SwapInInstruction { 608 | const DATA_LEN: usize = 8; 609 | 610 | // size = 1 or 3 611 | // flag[0/1], [account_size], [amount_in], [minium_amount_out] 612 | fn unpack(input: &[u8]) -> Result { 613 | if input.len() < SwapInInstruction::DATA_LEN { 614 | return Err(ProtocolError::InvalidInput.into()); 615 | } 616 | let &amount_in_arr = array_ref![input, 0, SwapInInstruction::DATA_LEN]; 617 | let amount_in = 618 | NonZeroU64::new(u64::from_le_bytes(amount_in_arr)).ok_or(ProtocolError::InvalidInput)?; 619 | Ok(Self { amount_in }) 620 | } 621 | } 622 | 623 | impl SwapOutInstruction { 624 | const DATA_LEN: usize = 16; 625 | 626 | // size = 1 or 3 627 | // flag[0/1], [account_size], [amount_in], [minium_amount_out] 628 | fn unpack(input: &[u8]) -> Result { 629 | if input.len() < SwapOutInstruction::DATA_LEN { 630 | return Err(ProtocolError::InvalidInput.into()); 631 | } 632 | let arr_data = array_ref![input, 0, SwapOutInstruction::DATA_LEN]; 633 | let (&expect_amount_out_arr, &minimum_amount_out_arr) = array_refs![arr_data, 8, 8]; 634 | let expect_amount_out = NonZeroU64::new(u64::from_le_bytes(expect_amount_out_arr)) 635 | .ok_or(ProtocolError::InvalidInput)?; 636 | let minimum_amount_out = NonZeroU64::new(u64::from_le_bytes(minimum_amount_out_arr)) 637 | .ok_or(ProtocolError::InvalidInput)?; 638 | if expect_amount_out.get() < minimum_amount_out.get() || expect_amount_out.get() == 0 { 639 | return Err(ProtocolError::InvalidExpectAmountOut.into()); 640 | } 641 | Ok(Self { 642 | expect_amount_out, 643 | minimum_amount_out, 644 | }) 645 | } 646 | } 647 | 648 | impl SwapOutSlimInstruction { 649 | const DATA_LEN: usize = 8; 650 | 651 | fn unpack(input: &[u8]) -> Result { 652 | if input.len() < SwapOutSlimInstruction::DATA_LEN { 653 | return Err(ProtocolError::InvalidInput.into()); 654 | } 655 | let &minimum_amount_out_arr = array_ref![input, 0, SwapOutSlimInstruction::DATA_LEN]; 656 | let minimum_amount_out = NonZeroU64::new(u64::from_le_bytes(minimum_amount_out_arr)) 657 | .ok_or(ProtocolError::InvalidInput)?; 658 | Ok(Self { minimum_amount_out }) 659 | } 660 | } 661 | 662 | #[cfg(test)] 663 | mod tests { 664 | use super::*; 665 | 666 | #[test] 667 | fn test_unpack_swap_token_swap() { 668 | let amount_in = 120000u64; 669 | let minimum_amount_out = 1080222u64; 670 | let expect_amount_out = 1090000u64; 671 | let mut buf = Vec::with_capacity(SwapInstruction::DATA_LEN); 672 | buf.extend_from_slice(&amount_in.to_le_bytes()); 673 | buf.extend_from_slice(&expect_amount_out.to_le_bytes()); 674 | buf.extend_from_slice(&minimum_amount_out.to_le_bytes()); 675 | // buf.insert(, element) 676 | 677 | let i = SwapInstruction::unpack(&buf[..]).unwrap(); 678 | assert_eq!(i.amount_in.get(), amount_in); 679 | assert_eq!(i.expect_amount_out.get(), expect_amount_out); 680 | assert_eq!(i.minimum_amount_out.get(), minimum_amount_out); 681 | } 682 | } 683 | -------------------------------------------------------------------------------- /src/program-rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! OnesolProtocol - DEX Aggregator 2 | 3 | mod constraints; 4 | pub mod error; 5 | mod exchanger; 6 | pub mod instruction; 7 | mod parser; 8 | pub mod processor; 9 | mod spl_token; 10 | pub mod state; 11 | 12 | #[cfg(not(feature = "no-entrypoint"))] 13 | mod entrypoint; 14 | 15 | // export 16 | pub use solana_program; 17 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/aldrin.rs: -------------------------------------------------------------------------------- 1 | use super::base::{validate_authority_pubkey, TokenAccount, TokenMint}; 2 | use crate::{ 3 | declare_validated_account_wrapper, 4 | error::{ProtocolError, ProtocolResult}, 5 | exchanger::aldrin::instruction::Side, 6 | }; 7 | use arrayref::array_ref; 8 | use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; 9 | 10 | declare_validated_account_wrapper!(AldrinPool, |account: &AccountInfo| { 11 | let account_data = account 12 | .try_borrow_data() 13 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 14 | if account_data.len() != 474 { 15 | return Err(ProtocolError::InvalidCremaSwapAccountData); 16 | } 17 | Ok(()) 18 | }); 19 | 20 | impl<'a, 'b: 'a> AldrinPool<'a, 'b> { 21 | pub fn nonce(self) -> ProtocolResult { 22 | Ok( 23 | self 24 | .inner() 25 | .try_borrow_data() 26 | .map_err(|_| ProtocolError::BorrowAccountDataError)?[232], 27 | ) 28 | } 29 | 30 | // pub fn authority(self) -> ProtocolResult { 31 | // let data = self 32 | // .inner() 33 | // .try_borrow_data() 34 | // .map_err(|_| ProtocolError::BorrowAccountDataError)?; 35 | // Ok(Pubkey::new_from_array(*array_ref![data, 240, 32])) 36 | // } 37 | 38 | // pub fn pool_signer(self) -> ProtocolResult { 39 | // let data = self 40 | // .inner() 41 | // .try_borrow_data() 42 | // .map_err(|_| ProtocolError::BorrowAccountDataError)?; 43 | // Ok(Pubkey::new_from_array(*array_ref![data, 200, 32])) 44 | // } 45 | 46 | pub fn coin_vault(self) -> ProtocolResult { 47 | let data = self 48 | .inner() 49 | .try_borrow_data() 50 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 51 | Ok(Pubkey::new_from_array(*array_ref![data, 72, 32])) 52 | } 53 | 54 | pub fn pc_vault(self) -> ProtocolResult { 55 | let data = self 56 | .inner() 57 | .try_borrow_data() 58 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 59 | Ok(Pubkey::new_from_array(*array_ref![data, 136, 32])) 60 | } 61 | 62 | pub fn coin_mint(self) -> ProtocolResult { 63 | let data = self 64 | .inner() 65 | .try_borrow_data() 66 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 67 | Ok(Pubkey::new_from_array(*array_ref![data, 104, 32])) 68 | } 69 | 70 | pub fn pc_mint(self) -> ProtocolResult { 71 | let data = self 72 | .inner() 73 | .try_borrow_data() 74 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 75 | Ok(Pubkey::new_from_array(*array_ref![data, 168, 32])) 76 | } 77 | 78 | pub fn pool_mint(self) -> ProtocolResult { 79 | let data = self 80 | .inner() 81 | .try_borrow_data() 82 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 83 | Ok(Pubkey::new_from_array(*array_ref![data, 40, 32])) 84 | } 85 | } 86 | 87 | #[derive(Copy, Clone)] 88 | pub struct AldrinPoolArgs<'a, 'b: 'a> { 89 | pub pool_info: AldrinPool<'a, 'b>, 90 | pub authority: &'a AccountInfo<'b>, 91 | pub pool_mint: TokenMint<'a, 'b>, 92 | pub pool_coin_vault: TokenAccount<'a, 'b>, 93 | pub pool_pc_vault: TokenAccount<'a, 'b>, 94 | pub fee_account: &'a AccountInfo<'b>, 95 | pub curve_key: &'a AccountInfo<'b>, 96 | pub program_id: &'a AccountInfo<'b>, 97 | } 98 | 99 | impl<'a, 'b: 'a> AldrinPoolArgs<'a, 'b> { 100 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 101 | const MIN_ACCOUNTS: usize = 8; 102 | 103 | if accounts.len() != MIN_ACCOUNTS { 104 | return Err(ProtocolError::InvalidAccountsLength); 105 | } 106 | let &[ 107 | ref pool_info_acc, 108 | ref authority, 109 | ref pool_mint_acc, 110 | ref pool_coin_vault_acc, 111 | ref pool_pc_vault_acc, 112 | ref fee_account, 113 | ref curve_key, 114 | ref program_id, 115 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 116 | 117 | let pool_info = AldrinPool::new(pool_info_acc)?; 118 | if !program_id.executable || *pool_info_acc.owner != *program_id.key { 119 | return Err(ProtocolError::InvalidProgramAddress); 120 | } 121 | 122 | if *pool_mint_acc.key != pool_info.pool_mint()? { 123 | return Err(ProtocolError::InvalidTokenMint); 124 | } 125 | let pool_vault_1 = TokenAccount::new(pool_coin_vault_acc)?; 126 | let pool_vault_2 = TokenAccount::new(pool_pc_vault_acc)?; 127 | 128 | let coin_mint = pool_info.coin_mint()?; 129 | let pc_mint = pool_info.pc_mint()?; 130 | 131 | // auto invert vault token account 132 | let (coin_vault, pc_vault) = 133 | if pool_vault_1.mint()? == coin_mint && pool_vault_2.mint()? == pc_mint { 134 | (pool_vault_1, pool_vault_2) 135 | } else if pool_vault_1.mint()? == pc_mint && pool_vault_2.mint()? == coin_mint { 136 | (pool_vault_2, pool_vault_1) 137 | } else { 138 | return Err(ProtocolError::InvalidTokenMint); 139 | }; 140 | 141 | if *coin_vault.inner().key != pool_info.coin_vault()? { 142 | msg!( 143 | "coin_vault got {}, expect: {}", 144 | coin_vault.inner().key, 145 | pool_info.coin_vault()? 146 | ); 147 | return Err(ProtocolError::InvalidTokenAccount); 148 | } 149 | 150 | if *pc_vault.inner().key != pool_info.pc_vault()? { 151 | msg!( 152 | "pc_vault got {}, expect: {}", 153 | pc_vault.inner().key, 154 | pool_info.pc_vault()? 155 | ); 156 | return Err(ProtocolError::InvalidTokenAccount); 157 | } 158 | 159 | validate_authority_pubkey( 160 | authority.key, 161 | program_id.key, 162 | &pool_info_acc.key.to_bytes(), 163 | pool_info.nonce()?, 164 | )?; 165 | 166 | Ok(Self { 167 | pool_info, 168 | authority, 169 | pool_mint: TokenMint::new(pool_mint_acc)?, 170 | pool_coin_vault: coin_vault, 171 | pool_pc_vault: pc_vault, 172 | fee_account, 173 | curve_key, 174 | program_id, 175 | }) 176 | } 177 | 178 | pub fn find_side(&self, source_mint: &Pubkey) -> ProtocolResult { 179 | let coin_mint = self.pool_info.coin_mint()?; 180 | if *source_mint == coin_mint { 181 | Ok(Side::Ask) 182 | } else { 183 | Ok(Side::Bid) 184 | } 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod test { 190 | use super::*; 191 | use bs58; 192 | use solana_sdk::{account::Account, account_info::IntoAccountInfo}; 193 | use std::str::FromStr; 194 | 195 | #[test] 196 | pub fn test_parse_aldrin_pool_info() { 197 | let pubkey = Pubkey::from_str("HjZ2zgg4HemPREiJ7he3VWqV6bHV5yhLNkMJahwigbzz").unwrap(); 198 | let program_id = Pubkey::from_str("CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4").unwrap(); 199 | let account_data = "4VMpc88zcKRkaAdBUkKmF6xLE8umnyqnBceSPeNcGdFdcHMnVMfjmCnJWCG3dVxvE8LzuisKJDY5cRkFwCpfWM7NtKSKc3dw 200 | aqfLsNsLiR9TZ5gQrptWrv3DKD4GeTc9iUv8ftfEjKARGg4zGhpMzbDWFnsZtL19VU94iCuRfTaspLKEdpAW7qp7KhA3xM4YWBA4d2iPBu1cuQFMiAMocXU4 201 | 9YqBeEhajTLbcckBXsnYgN5KhWmcFtRwgzKSEuG3nnu8HDpx5ze8EW1PzYGg2mCsx4KnMUh7prqW2YKuXnrcwBwfe1PMDKdTxCrY17r9tPmaQ3vR4xv7RJA9 202 | GdLrPf1C84LpFgUkbJ72DEgL7PyiXF2tuJofzrt8PXxzWHjL3YPcbJyNtaEWEmem4HwMbw6JYing6X422pLnXAb1zeyGnE1oM4s7d7MFysZ1FMfpWgYJvaD7 203 | 11EtACBHwDCPbbcwi588Pdgu1SCHyzCMyX8t8RnShRJGPTwrfDLizxxTQxHTAXRCSMPtJ4RnBYLUwwxCgcPUYRJiFWpV7CuFWMNTxw2rs8skuvYPFh1fj7E2 204 | dVmzXNyJydYDyCE8ntSuc6NQJAnmNYnpMueCof7KfJWJuxVbkZ2jKyWMe349VHLS28sd1Kon"; 205 | let mut test_account = Account { 206 | lamports: 4189920, 207 | data: bs58::decode(account_data.replace('\n', "")) 208 | .into_vec() 209 | .unwrap(), 210 | owner: program_id, 211 | executable: false, 212 | rent_epoch: 281, 213 | }; 214 | let account_info = (&pubkey, &mut test_account).into_account_info(); 215 | assert!(*account_info.key == pubkey); 216 | let c = AldrinPool::new(&account_info).unwrap(); 217 | assert_eq!( 218 | c.coin_vault().unwrap().to_string(), 219 | "7auinu2nWzoYfp8NWjGmHrhZgv5X4T9GCfHGT5TRj8tm".to_string() 220 | ); 221 | assert_eq!( 222 | c.coin_mint().unwrap().to_string(), 223 | "7zhbkbKpGaUsJW7AD4yyAfGGoy53Xx2H3Ai5BKcwGKHw".to_string() 224 | ); 225 | assert_eq!( 226 | c.pc_vault().unwrap().to_string(), 227 | "C1j8rurz8aTkjCReDwaHFu6M6j1h8eBEAGrvoMfccwLS".to_string() 228 | ); 229 | assert_eq!( 230 | c.pc_mint().unwrap().to_string(), 231 | "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string() 232 | ); 233 | assert_eq!( 234 | c.pool_mint().unwrap().to_string(), 235 | "8CFVFQGtqdnuYgypdJ2YwexRuaFs9KUPSyP6Gd5LtTqL".to_string() 236 | ); 237 | assert_eq!(c.nonce().unwrap(), 252); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/base.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | declare_validated_account_wrapper, 3 | error::{ProtocolError, ProtocolResult}, 4 | spl_token, 5 | state::SwapInfo, 6 | }; 7 | use arrayref::{array_ref, array_refs}; 8 | use solana_program::{account_info::AccountInfo, msg, program_pack::Pack, pubkey::Pubkey, sysvar}; 9 | 10 | declare_validated_account_wrapper!(SplTokenProgram, |account: &AccountInfo| { 11 | if *account.key != spl_token::ID { 12 | return Err(ProtocolError::IncorrectTokenProgramId); 13 | }; 14 | Ok(()) 15 | }); 16 | 17 | declare_validated_account_wrapper!(TokenAccount, |account: &AccountInfo| { 18 | if *account.owner != spl_token::ID { 19 | return Err(ProtocolError::InvalidTokenAccount); 20 | } 21 | let data = account 22 | .try_borrow_data() 23 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 24 | if data.len() != spl_token::ACCOUNT_LEN { 25 | return Err(ProtocolError::InvalidTokenAccount); 26 | }; 27 | let is_initialized = data[0x6c]; 28 | if is_initialized != 1u8 { 29 | return Err(ProtocolError::InvalidTokenAccount); 30 | }; 31 | Ok(()) 32 | }); 33 | 34 | #[allow(unused)] 35 | impl<'a, 'b: 'a> TokenAccount<'a, 'b> { 36 | pub fn balance(self) -> ProtocolResult { 37 | let data = self 38 | .inner() 39 | .try_borrow_data() 40 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 41 | Ok(u64::from_le_bytes(*array_ref![data, 64, 8])) 42 | } 43 | pub fn mint(self) -> ProtocolResult { 44 | let data = self 45 | .inner() 46 | .try_borrow_data() 47 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 48 | Ok(Pubkey::new_from_array(*array_ref![data, 0, 32])) 49 | } 50 | 51 | pub fn owner(self) -> ProtocolResult { 52 | let data = self 53 | .inner() 54 | .try_borrow_data() 55 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 56 | Ok(Pubkey::new_from_array(*array_ref![data, 32, 32])) 57 | } 58 | 59 | pub fn delegate(self) -> ProtocolResult> { 60 | let data = self 61 | .inner() 62 | .try_borrow_data() 63 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 64 | unpack_coption_key(array_ref![data, 72, 36]) 65 | } 66 | 67 | pub fn check_owner(self, authority: &Pubkey, strict: bool) -> ProtocolResult<()> { 68 | let owner = self.owner()?; 69 | if *authority == owner { 70 | return Ok(()); 71 | } 72 | if strict { 73 | return Err(ProtocolError::InvalidOwner); 74 | } 75 | let delegate = self.delegate()?; 76 | if let Some(d) = delegate { 77 | if d == *authority { 78 | return Ok(()); 79 | } 80 | } 81 | Err(ProtocolError::InvalidOwner) 82 | } 83 | 84 | // pub fn check_delegate(self, authority: &Pubkey) -> ProtocolResult<()> { 85 | // let delegate = self.delegate()?; 86 | // match delegate { 87 | // Some(d) => { 88 | // if d == *authority { 89 | // return Ok(()); 90 | // } 91 | // } 92 | // None => {} 93 | // } 94 | // Err(ProtocolError::InvalidDelegate) 95 | // } 96 | } 97 | 98 | declare_validated_account_wrapper!(TokenMint, |mint: &AccountInfo| { 99 | if *mint.owner != spl_token::ID { 100 | return Err(ProtocolError::InvalidTokenMint); 101 | }; 102 | let data = mint 103 | .try_borrow_data() 104 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 105 | if data.len() != spl_token::MINT_LEN { 106 | return Err(ProtocolError::InvalidTokenMint); 107 | }; 108 | let is_initialized = data[0x2d]; 109 | if is_initialized != 1u8 { 110 | return Err(ProtocolError::InvalidTokenMint); 111 | }; 112 | Ok(()) 113 | }); 114 | 115 | declare_validated_account_wrapper!(SignerAccount, |account: &AccountInfo| { 116 | if !account.is_signer { 117 | return Err(ProtocolError::InvalidSignerAccount); 118 | } 119 | Ok(()) 120 | }); 121 | 122 | declare_validated_account_wrapper!(SysClockAccount, |account: &AccountInfo| { 123 | if *account.key != sysvar::clock::id() { 124 | return Err(ProtocolError::InvalidClockAccount); 125 | } 126 | Ok(()) 127 | }); 128 | 129 | #[derive(Copy, Clone)] 130 | pub struct UserArgs<'a, 'b: 'a> { 131 | pub token_source_account: TokenAccount<'a, 'b>, 132 | pub token_destination_account: TokenAccount<'a, 'b>, 133 | pub source_account_owner: &'a AccountInfo<'b>, 134 | } 135 | 136 | impl<'a, 'b: 'a> UserArgs<'a, 'b> { 137 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 138 | const MIN_ACCOUNTS: usize = 3; 139 | if accounts.len() != MIN_ACCOUNTS { 140 | return Err(ProtocolError::InvalidAccountsLength); 141 | } 142 | 143 | let &[ 144 | ref token_source_acc_info, 145 | ref token_destination_acc_info, 146 | ref source_account_owner, 147 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 148 | 149 | let token_source_account = TokenAccount::new(token_source_acc_info)?; 150 | let token_destination_account = TokenAccount::new(token_destination_acc_info)?; 151 | // let source_account_owner = SignerAccount::new(source_account_owner)?; 152 | token_source_account.check_owner(source_account_owner.key, false)?; 153 | 154 | if token_source_account.mint() == token_destination_account.mint() { 155 | return Err(ProtocolError::InvalidTokenAccount); 156 | } 157 | 158 | Ok(UserArgs { 159 | token_source_account, 160 | token_destination_account, 161 | source_account_owner, 162 | }) 163 | } 164 | } 165 | 166 | #[derive(Copy, Clone)] 167 | pub struct TokenAccountAndMint<'a, 'b: 'a> { 168 | pub account: TokenAccount<'a, 'b>, 169 | pub mint: TokenMint<'a, 'b>, 170 | } 171 | 172 | #[allow(unused)] 173 | impl<'a, 'b: 'a> TokenAccountAndMint<'a, 'b> { 174 | pub fn new(account: TokenAccount<'a, 'b>, mint: TokenMint<'a, 'b>) -> ProtocolResult { 175 | if *mint.pubkey() != account.mint()? { 176 | return Err(ProtocolError::InvalidTokenMint); 177 | } 178 | Ok(TokenAccountAndMint { account, mint }) 179 | } 180 | 181 | pub fn get_account(self) -> TokenAccount<'a, 'b> { 182 | self.account 183 | } 184 | 185 | pub fn get_mint(self) -> TokenMint<'a, 'b> { 186 | self.mint 187 | } 188 | } 189 | 190 | pub struct SwapInfoArgs<'a, 'b: 'a> { 191 | pub swap_info: SwapInfo, 192 | pub swap_info_acc: &'a AccountInfo<'b>, 193 | } 194 | 195 | impl<'a, 'b: 'a> SwapInfoArgs<'a, 'b> { 196 | pub fn with_parsed_args( 197 | account: &'a AccountInfo<'b>, 198 | program_id: &'a Pubkey, 199 | ) -> ProtocolResult { 200 | if *account.owner != *program_id { 201 | return Err(ProtocolError::InvalidOwner); 202 | } 203 | let swap_info = 204 | SwapInfo::unpack(&account.data.borrow()).map_err(|_| ProtocolError::InvalidAccountData)?; 205 | Ok(Self { 206 | swap_info, 207 | swap_info_acc: account, 208 | }) 209 | } 210 | } 211 | 212 | #[allow(unused)] 213 | fn unpack_coption_key(src: &[u8; 36]) -> ProtocolResult> { 214 | let (tag, body) = array_refs![src, 4, 32]; 215 | match *tag { 216 | [0, 0, 0, 0] => Ok(None), 217 | [1, 0, 0, 0] => Ok(Some(Pubkey::new_from_array(*body))), 218 | _ => Err(ProtocolError::InvalidAccountData), 219 | } 220 | } 221 | 222 | #[allow(dead_code)] 223 | /// Calculates the authority id by generating a program address. 224 | pub fn validate_authority_pubkey( 225 | authority: &Pubkey, 226 | program_id: &Pubkey, 227 | base_key: &[u8], 228 | nonce: u8, 229 | ) -> Result<(), ProtocolError> { 230 | let key = Pubkey::create_program_address(&[base_key, &[nonce]], program_id).map_err(|e| { 231 | msg!("create_program_address failed: {}, nonce: {}", e, nonce); 232 | ProtocolError::InvalidProgramAddress 233 | })?; 234 | if key != *authority { 235 | return Err(ProtocolError::InvalidAuthority); 236 | } 237 | Ok(()) 238 | } 239 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/crema.rs: -------------------------------------------------------------------------------- 1 | use super::base::TokenAccount; 2 | use crate::{ 3 | declare_validated_account_wrapper, 4 | error::{ProtocolError, ProtocolResult}, 5 | parser::base::validate_authority_pubkey, 6 | }; 7 | use arrayref::array_ref; 8 | use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; 9 | 10 | declare_validated_account_wrapper!(SwapInfoV1, |account: &AccountInfo| { 11 | let account_data = account 12 | .try_borrow_data() 13 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 14 | if account_data.len() != 473 { 15 | return Err(ProtocolError::InvalidCremaSwapAccountData); 16 | } 17 | if account_data[34] != 1 { 18 | return Err(ProtocolError::InvalidCremaSwapAccountData); 19 | } 20 | Ok(()) 21 | }); 22 | 23 | impl<'a, 'b: 'a> SwapInfoV1<'a, 'b> { 24 | #[allow(dead_code)] 25 | pub fn nonce(self) -> ProtocolResult { 26 | Ok( 27 | self 28 | .inner() 29 | .try_borrow_data() 30 | .map_err(|_| ProtocolError::BorrowAccountDataError)?[35], 31 | ) 32 | } 33 | 34 | pub fn token_a(self) -> ProtocolResult { 35 | let data = self 36 | .inner() 37 | .try_borrow_data() 38 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 39 | Ok(Pubkey::new_from_array(*array_ref![data, 164, 32])) 40 | } 41 | 42 | pub fn token_b(self) -> ProtocolResult { 43 | let data = self 44 | .inner() 45 | .try_borrow_data() 46 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 47 | Ok(Pubkey::new_from_array(*array_ref![data, 196, 32])) 48 | } 49 | 50 | pub fn token_a_mint(self) -> ProtocolResult { 51 | let data = self 52 | .inner() 53 | .try_borrow_data() 54 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 55 | Ok(Pubkey::new_from_array(*array_ref![data, 228, 32])) 56 | } 57 | 58 | pub fn token_b_mint(self) -> ProtocolResult { 59 | let data = self 60 | .inner() 61 | .try_borrow_data() 62 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 63 | Ok(Pubkey::new_from_array(*array_ref![data, 260, 32])) 64 | } 65 | } 66 | 67 | #[derive(Copy, Clone)] 68 | pub struct CremaSwapV1Args<'a, 'b: 'a> { 69 | pub swap_info: SwapInfoV1<'a, 'b>, 70 | pub authority: &'a AccountInfo<'b>, 71 | pub pool_token_a: TokenAccount<'a, 'b>, 72 | pub pool_token_b: TokenAccount<'a, 'b>, 73 | pub tick_dst: &'a AccountInfo<'b>, 74 | pub program_id: &'a AccountInfo<'b>, 75 | } 76 | 77 | impl<'a, 'b: 'a> CremaSwapV1Args<'a, 'b> { 78 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 79 | const MIN_ACCOUNTS: usize = 6; 80 | 81 | if accounts.len() != MIN_ACCOUNTS { 82 | return Err(ProtocolError::InvalidAccountsLength); 83 | } 84 | let &[ 85 | ref swap_info_acc, 86 | ref authority, 87 | ref pool_token_a_acc, 88 | ref pool_token_b_acc, 89 | ref tick_dst_acc, 90 | ref program_id, 91 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 92 | 93 | if !swap_info_acc.is_writable { 94 | return Err(ProtocolError::ReadonlyAccount); 95 | } 96 | let swap_info = SwapInfoV1::new(swap_info_acc)?; 97 | if !program_id.executable || *swap_info_acc.owner != *program_id.key { 98 | msg!( 99 | "program_id: {}, executable: {}, swap_info: {}, owner: {}", 100 | program_id.key.to_string(), 101 | program_id.executable, 102 | swap_info_acc.key.to_string(), 103 | swap_info_acc.owner.to_string(), 104 | ); 105 | return Err(ProtocolError::InvalidProgramAddress); 106 | } 107 | 108 | validate_authority_pubkey( 109 | authority.key, 110 | program_id.key, 111 | &swap_info_acc.key.to_bytes(), 112 | swap_info.nonce()?, 113 | )?; 114 | let swap_token_a = swap_info.token_a()?; 115 | let swap_token_b = swap_info.token_b()?; 116 | 117 | let (pool_token_a, pool_token_b) = 118 | if *pool_token_a_acc.key == swap_token_a && *pool_token_b_acc.key == swap_token_b { 119 | ( 120 | TokenAccount::new(pool_token_a_acc)?, 121 | TokenAccount::new(pool_token_b_acc)?, 122 | ) 123 | } else if *pool_token_a_acc.key == swap_token_b && *pool_token_b_acc.key == swap_token_a { 124 | ( 125 | TokenAccount::new(pool_token_b_acc)?, 126 | TokenAccount::new(pool_token_a_acc)?, 127 | ) 128 | } else { 129 | return Err(ProtocolError::InvalidTokenAccount); 130 | }; 131 | 132 | Ok(Self { 133 | swap_info, 134 | authority, 135 | pool_token_a, 136 | pool_token_b, 137 | tick_dst: tick_dst_acc, 138 | program_id, 139 | }) 140 | } 141 | 142 | pub fn find_token_pair( 143 | &self, 144 | source_mint_key: &Pubkey, 145 | destination_mint_key: &Pubkey, 146 | ) -> ProtocolResult<(&TokenAccount<'a, 'b>, &TokenAccount<'a, 'b>)> { 147 | let pool_token_a_mint = self.swap_info.token_a_mint()?; 148 | let pool_token_b_mint = self.swap_info.token_b_mint()?; 149 | if *source_mint_key == pool_token_a_mint && *destination_mint_key == pool_token_b_mint { 150 | return Ok((&self.pool_token_a, &self.pool_token_b)); 151 | } else if *source_mint_key == pool_token_b_mint && *destination_mint_key == pool_token_a_mint { 152 | return Ok((&self.pool_token_b, &self.pool_token_a)); 153 | } 154 | Err(ProtocolError::InvalidTokenMint) 155 | } 156 | } 157 | 158 | #[cfg(test)] 159 | mod test { 160 | use super::*; 161 | use bs58; 162 | use solana_sdk::{account::Account, account_info::IntoAccountInfo}; 163 | use std::str::FromStr; 164 | 165 | #[test] 166 | pub fn test_parse_crema_swap_v1() { 167 | let pubkey = Pubkey::from_str("8J3avAjuRfL2CYFKKDwhhceiRoajhrHv9kN5nUiEnuBG").unwrap(); 168 | let program_id = Pubkey::from_str("6MLxLqiXaaSUpkgMnWDTuejNZEz3kE7k2woyHGVFw319").unwrap(); 169 | let account_data = "GfbXvUuzWx8PEGeQR41UGuxsUTmM7kYjMA5BZZoQv9MAGkNCeEkfcusa5rLVifmCQRSPr8vPwQ8wRFAzuGSXGgH4wUKBph 170 | CBDT9quQHAAvBLUJTqMXaSqYjNtq9s3QSZHsCZE1HA8iBHBUgZzW79KnBqHPEnpENxcsN2fAeM4ZtnptTrTYyvnNHjzkfK15jPhXeBntuYRnrubVfYs5HL8X 171 | WVZrUsGc2FiNmw9DxsgctR1pJUfkqqkUSvXUywbDnSVwgJpjCQUTWJYwGUCfWyKcjezWvVuRJaobis634fDApe3SmXJEFo5KiT3hgVCJWiZcRCie4wR3daiR 172 | YZybDHAn6bUYwVN82MRcq4EyiZrChSXgu3S67uiLfDnR3Wfmgn6nCZG2UnuYT6MiASsNDdxVP2RjMquLYkL8ZU2RHUvVLYUNfXpJArnt95ByCXA9zv4DhRUh 173 | SaE3zxQ9yT9m4eBR3rqsmxsjdpWv7EPezNnqiuKJjWNMrxrEb77ecX6UpsdVn6LWJWKtU67Ug6DjKYGGVcrCw4T7ZGppQr6y5pvXYQLe42RFUh77Jvm6CKqc 174 | WExa6Gae6euRW6eCcTw5Lf4F7y6PZxD3wek4uMrrHnURYHBkaumuCDiy1z3kbrv9R9RGsYT"; 175 | let mut test_account = Account { 176 | lamports: 4182960, 177 | data: bs58::decode(account_data.replace('\n', "")) 178 | .into_vec() 179 | .unwrap(), 180 | owner: program_id, 181 | executable: false, 182 | rent_epoch: 281, 183 | }; 184 | let account_info = (&pubkey, &mut test_account).into_account_info(); 185 | assert!(*account_info.key == pubkey); 186 | let c = SwapInfoV1::new(&account_info).unwrap(); 187 | assert_eq!( 188 | c.token_a().unwrap().to_string(), 189 | "FAqsr5LhMZQMYwxXrQuCH5C6bx1mVwuXG3WiQ5YjCEzk".to_string() 190 | ); 191 | assert_eq!( 192 | c.token_b().unwrap().to_string(), 193 | "DwFzRnWVxpvrrMJuQUwhBXhPhqUPMbrmDVJAt75k5ybE".to_string() 194 | ); 195 | assert_eq!( 196 | c.token_a_mint().unwrap().to_string(), 197 | "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB".to_string() 198 | ); 199 | assert_eq!( 200 | c.token_b_mint().unwrap().to_string(), 201 | "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string() 202 | ); 203 | assert_eq!(c.nonce().unwrap(), 254,); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/cropper.rs: -------------------------------------------------------------------------------- 1 | use super::base::{validate_authority_pubkey, TokenAccount, TokenMint}; 2 | use crate::{ 3 | declare_validated_account_wrapper, 4 | error::{ProtocolError, ProtocolResult}, 5 | }; 6 | use arrayref::array_ref; 7 | use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; 8 | 9 | declare_validated_account_wrapper!(CropperSwapV1, |account: &AccountInfo| { 10 | let account_data = account 11 | .try_borrow_data() 12 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 13 | if account_data.len() != 291 { 14 | return Err(ProtocolError::InvalidCremaSwapAccountData); 15 | } 16 | Ok(()) 17 | }); 18 | 19 | impl<'a, 'b: 'a> CropperSwapV1<'a, 'b> { 20 | pub fn nonce(self) -> ProtocolResult { 21 | Ok( 22 | self 23 | .inner() 24 | .try_borrow_data() 25 | .map_err(|_| ProtocolError::BorrowAccountDataError)?[2], 26 | ) 27 | } 28 | 29 | pub fn token_a_account(self) -> ProtocolResult { 30 | let data = self 31 | .inner() 32 | .try_borrow_data() 33 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 34 | Ok(Pubkey::new_from_array(*array_ref![data, 131, 32])) 35 | } 36 | 37 | pub fn token_b_account(self) -> ProtocolResult { 38 | let data = self 39 | .inner() 40 | .try_borrow_data() 41 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 42 | Ok(Pubkey::new_from_array(*array_ref![data, 163, 32])) 43 | } 44 | 45 | pub fn token_a_mint(self) -> ProtocolResult { 46 | let data = self 47 | .inner() 48 | .try_borrow_data() 49 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 50 | Ok(Pubkey::new_from_array(*array_ref![data, 227, 32])) 51 | } 52 | 53 | pub fn token_b_mint(self) -> ProtocolResult { 54 | let data = self 55 | .inner() 56 | .try_borrow_data() 57 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 58 | Ok(Pubkey::new_from_array(*array_ref![data, 259, 32])) 59 | } 60 | 61 | pub fn pool_mint(self) -> ProtocolResult { 62 | let data = self 63 | .inner() 64 | .try_borrow_data() 65 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 66 | Ok(Pubkey::new_from_array(*array_ref![data, 195, 32])) 67 | } 68 | } 69 | 70 | declare_validated_account_wrapper!(CropperProgramState, |account: &AccountInfo| { 71 | let account_data = account 72 | .try_borrow_data() 73 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 74 | if account_data.len() != 130 { 75 | return Err(ProtocolError::InvalidCremaSwapAccountData); 76 | } 77 | Ok(()) 78 | }); 79 | 80 | impl<'a, 'b: 'a> CropperProgramState<'a, 'b> { 81 | pub fn fee_owner(self) -> ProtocolResult { 82 | let data = self 83 | .inner() 84 | .try_borrow_data() 85 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 86 | Ok(Pubkey::new_from_array(*array_ref![data, 33, 32])) 87 | } 88 | } 89 | 90 | #[derive(Copy, Clone)] 91 | pub struct CropperArgs<'a, 'b: 'a> { 92 | pub swap_info: CropperSwapV1<'a, 'b>, 93 | pub authority: &'a AccountInfo<'b>, 94 | pub program_state: CropperProgramState<'a, 'b>, 95 | pub token_a_account: TokenAccount<'a, 'b>, 96 | pub token_b_account: TokenAccount<'a, 'b>, 97 | pub pool_mint: TokenMint<'a, 'b>, 98 | pub fee_account: TokenAccount<'a, 'b>, 99 | pub program_id: &'a AccountInfo<'b>, 100 | } 101 | 102 | impl<'a, 'b: 'a> CropperArgs<'a, 'b> { 103 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 104 | const MIN_ACCOUNTS: usize = 8; 105 | 106 | if accounts.len() != MIN_ACCOUNTS { 107 | return Err(ProtocolError::InvalidAccountsLength); 108 | } 109 | let &[ 110 | ref swap_info_acc, 111 | ref authority, 112 | ref program_state_acc, 113 | ref token_a_account_acc, 114 | ref token_b_account_acc, 115 | ref pool_mint_acc, 116 | ref fee_account_acc, 117 | ref program_id, 118 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 119 | 120 | let swap_info = CropperSwapV1::new(swap_info_acc)?; 121 | if !program_id.executable || *swap_info_acc.owner != *program_id.key { 122 | return Err(ProtocolError::InvalidProgramAddress); 123 | } 124 | 125 | if *pool_mint_acc.key != swap_info.pool_mint()? { 126 | return Err(ProtocolError::InvalidPoolMint); 127 | } 128 | let token_1_account = TokenAccount::new(token_a_account_acc)?; 129 | let token_2_account = TokenAccount::new(token_b_account_acc)?; 130 | 131 | let pool_token_a = swap_info.token_a_account()?; 132 | let pool_token_b = swap_info.token_b_account()?; 133 | 134 | // auto invert vault token account 135 | let (token_a_account, token_b_account) = if *token_1_account.pubkey() == pool_token_a 136 | && *token_2_account.pubkey() == pool_token_b 137 | { 138 | (token_1_account, token_2_account) 139 | } else if *token_1_account.pubkey() == pool_token_b && *token_2_account.pubkey() == pool_token_a 140 | { 141 | (token_2_account, token_1_account) 142 | } else { 143 | return Err(ProtocolError::InvalidTokenMint); 144 | }; 145 | 146 | validate_authority_pubkey( 147 | authority.key, 148 | program_id.key, 149 | &swap_info_acc.key.to_bytes(), 150 | swap_info.nonce()?, 151 | )?; 152 | 153 | let program_state = CropperProgramState::new(program_state_acc)?; 154 | let fee_account = TokenAccount::new(fee_account_acc)?; 155 | 156 | if fee_account.owner()? != program_state.fee_owner()? { 157 | msg!( 158 | "cropper_finance, fee_account.owner[{}] != program_state.fee_owner[{}]", 159 | fee_account.owner()?, 160 | program_state.fee_owner()? 161 | ); 162 | } 163 | let pool_mint = TokenMint::new(pool_mint_acc)?; 164 | if swap_info.pool_mint()? != *pool_mint.pubkey() { 165 | msg!( 166 | "cropper_finance, pool_mint is {}, expect {}", 167 | pool_mint.pubkey(), 168 | swap_info.pool_mint()? 169 | ); 170 | } 171 | 172 | Ok(Self { 173 | swap_info, 174 | authority, 175 | program_state, 176 | token_a_account, 177 | token_b_account, 178 | pool_mint, 179 | fee_account, 180 | program_id, 181 | }) 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod test { 187 | use super::*; 188 | use bs58; 189 | use solana_sdk::{account::Account, account_info::IntoAccountInfo}; 190 | use std::str::FromStr; 191 | 192 | #[test] 193 | pub fn test_parse_cropper_swap_info() { 194 | let pubkey = Pubkey::from_str("7NWyuTfpb8gfRpgm67yv5GkdX2EM3WkefGSwHZfNVzTW").unwrap(); 195 | let program_id = Pubkey::from_str("CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh").unwrap(); 196 | let account_data = "2C1RW18oraJgyUDV6gjSYhkVvyAJktsiS4Hq3dn2J6HQn3EDc1L9HW8uVLZPAGvfJxLeVQBinxLGGpcySQeD1sfrUiPRYy3u 197 | GqmEhSz8LxYtVh2a8qBpQwPnrExV2EGqdvii6s3KUdxayiDAiEv8pUoF5xDHGQNHwYnA8r76yiFkc8RMom5pahKvqH4vBeJ2ypMBCqXos98PB4p9s7HanZQJ 198 | wwNsNLBhoPbzt4ETyew4TPGnb5dAuvQtmLRmHmiMrMv4hjcbn3yBYrtzyfFs774i28HRTL9n9S3DbgYsUmJPBBbJjU3TaJBxLyiWASQDrd4snbxpcWbgTo95 199 | WiQ3pv9mtcjZxcGchY1hw4AGj83tmHeah5EE5cRWrhqemnT9TZLoFHzoVRZBW"; 200 | let mut test_account = Account { 201 | lamports: 2916240, 202 | data: bs58::decode(account_data.replace('\n', "")) 203 | .into_vec() 204 | .unwrap(), 205 | owner: program_id, 206 | executable: false, 207 | rent_epoch: 283, 208 | }; 209 | let account_info = (&pubkey, &mut test_account).into_account_info(); 210 | assert!(*account_info.key == pubkey); 211 | let c = CropperSwapV1::new(&account_info).unwrap(); 212 | assert_eq!( 213 | c.token_a_account().unwrap().to_string(), 214 | "5DuRdWMtLQ51Ld534PsjDbudPGVnYkCwiGKo5EKcoaaL".to_string() 215 | ); 216 | assert_eq!( 217 | c.token_b_account().unwrap().to_string(), 218 | "4G9Hp77tNNfMuYgD2DfFxZAAaruXuJigfbU2FjBreSdn".to_string() 219 | ); 220 | assert_eq!( 221 | c.token_a_mint().unwrap().to_string(), 222 | "So11111111111111111111111111111111111111112".to_string() 223 | ); 224 | assert_eq!( 225 | c.token_b_mint().unwrap().to_string(), 226 | "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string() 227 | ); 228 | assert_eq!( 229 | c.pool_mint().unwrap().to_string(), 230 | "APTaiNJxUtAZMnhoZCVXdxR5kf7ExYWuET3sfnub59z2".to_string() 231 | ); 232 | assert_eq!(c.nonce().unwrap(), 253); 233 | } 234 | 235 | #[test] 236 | pub fn test_parse_cropper_program_state() { 237 | let pubkey = Pubkey::from_str("3hsU1VgsBgBgz5jWiqdw9RfGU6TpWdCmdah1oi4kF3Tq").unwrap(); 238 | let program_id = Pubkey::from_str("CTMAxxk34HjKWxQ3QLZK1HpaLXmBveao3ESePXbiyfzh").unwrap(); 239 | let account_data = "49Njgem1Ug3UieKyzb639EJxjKFgsi5hrc3W9TW1wCK7aPTavf6uz7kYFur1f6jo1QKZEseY9EBHDEmrkim8Wc9cb7f7sACW 240 | y2oAoS9LLHyqQpzfLNY48sxw48PxhFPsyfVVasbjFrygCNJjcoNsQqs9UZ1rJYbAGvuf1vosp3zKkLnxF"; 241 | let mut test_account = Account { 242 | lamports: 1795680, 243 | data: bs58::decode(account_data.replace('\n', "")) 244 | .into_vec() 245 | .unwrap(), 246 | owner: program_id, 247 | executable: false, 248 | rent_epoch: 284, 249 | }; 250 | let account_info = (&pubkey, &mut test_account).into_account_info(); 251 | assert!(*account_info.key == pubkey); 252 | let c = CropperProgramState::new(&account_info).unwrap(); 253 | assert_eq!( 254 | c.fee_owner().unwrap().to_string(), 255 | "DyDdJM9KVsvosfXbcHDp4pRpmbMHkRq3pcarBykPy4ir".to_string() 256 | ); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aldrin; 2 | pub mod base; 3 | pub mod crema; 4 | pub mod cropper; 5 | pub mod raydium; 6 | pub mod serum_dex; 7 | pub mod spl_token_swap; 8 | pub mod stable_swap; 9 | 10 | #[macro_export] 11 | macro_rules! declare_validated_account_wrapper { 12 | ($WrapperT:ident, $validate:expr $(, $a:ident : $t:ty)*) => { 13 | #[derive(Copy, Clone)] 14 | pub struct $WrapperT<'a, 'b: 'a>(&'a AccountInfo<'b>); 15 | impl<'a, 'b: 'a> $WrapperT<'a, 'b> { 16 | #[allow(unused)] 17 | pub fn new(account: &'a AccountInfo<'b> $(,$a: $t)*) -> ProtocolResult { 18 | let validate_result: ProtocolResult = $validate(account $(,$a)*); 19 | validate_result?; 20 | Ok($WrapperT(account)) 21 | } 22 | 23 | #[inline(always)] 24 | #[allow(unused)] 25 | pub fn inner(self) -> &'a AccountInfo<'b> { 26 | self.0 27 | } 28 | 29 | #[inline(always)] 30 | #[allow(unused)] 31 | pub fn pubkey(self) -> &'b Pubkey { 32 | self.0.key 33 | } 34 | 35 | #[inline(always)] 36 | #[allow(unused)] 37 | pub fn check_writable(self) -> ProtocolResult<()> { 38 | if !self.inner().is_writable { 39 | return Err(ProtocolError::ReadonlyAccount) 40 | } 41 | Ok(()) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/raydium.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | declare_validated_account_wrapper, 3 | error::{ProtocolError, ProtocolResult}, 4 | }; 5 | use arrayref::array_ref; 6 | use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; 7 | 8 | use super::{ 9 | base::TokenAccount, 10 | serum_dex::{SerumDexMarket, SerumDexOpenOrders}, 11 | }; 12 | 13 | declare_validated_account_wrapper!(RaydiumAmmInfo, |account: &AccountInfo| { 14 | const DATA_LEN: usize = 752; 15 | let data = account 16 | .try_borrow_data() 17 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 18 | if data.len() != DATA_LEN { 19 | return Err(ProtocolError::InvalidTokenAccount); 20 | }; 21 | let status = u64::from_le_bytes(*array_ref![data, 0, 8]); 22 | if status != 1u64 { 23 | return Err(ProtocolError::InvalidAccountFlags); 24 | }; 25 | Ok(()) 26 | }); 27 | 28 | #[allow(dead_code)] 29 | impl<'a, 'b: 'a> RaydiumAmmInfo<'a, 'b> { 30 | pub fn token_coin(self) -> ProtocolResult { 31 | let data = self 32 | .inner() 33 | .try_borrow_data() 34 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 35 | // 128 + 208 36 | Ok(Pubkey::new_from_array(*array_ref![data, 336, 32])) 37 | } 38 | 39 | pub fn token_pc(self) -> ProtocolResult { 40 | let data = self 41 | .inner() 42 | .try_borrow_data() 43 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 44 | // 128 + 208 45 | Ok(Pubkey::new_from_array(*array_ref![data, 368, 32])) 46 | } 47 | 48 | pub fn coin_mint(self) -> ProtocolResult { 49 | let data = self 50 | .inner() 51 | .try_borrow_data() 52 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 53 | // 128 + 208 54 | Ok(Pubkey::new_from_array(*array_ref![data, 400, 32])) 55 | } 56 | 57 | pub fn pc_mint(self) -> ProtocolResult { 58 | let data = self 59 | .inner() 60 | .try_borrow_data() 61 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 62 | // 128 + 208 63 | Ok(Pubkey::new_from_array(*array_ref![data, 432, 32])) 64 | } 65 | 66 | pub fn open_orders(self) -> ProtocolResult { 67 | let data = self 68 | .inner() 69 | .try_borrow_data() 70 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 71 | // 128 + 208 72 | Ok(Pubkey::new_from_array(*array_ref![data, 496, 32])) 73 | } 74 | 75 | pub fn market(self) -> ProtocolResult { 76 | let data = self 77 | .inner() 78 | .try_borrow_data() 79 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 80 | // 128 + 208 81 | Ok(Pubkey::new_from_array(*array_ref![data, 528, 32])) 82 | } 83 | 84 | pub fn serum_dex(self) -> ProtocolResult { 85 | let data = self 86 | .inner() 87 | .try_borrow_data() 88 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 89 | // 128 + 208 90 | Ok(Pubkey::new_from_array(*array_ref![data, 560, 32])) 91 | } 92 | } 93 | 94 | #[derive(Copy, Clone)] 95 | pub struct RaydiumSwapArgs<'a, 'b: 'a> { 96 | pub amm_info: RaydiumAmmInfo<'a, 'b>, 97 | pub authority: &'a AccountInfo<'b>, 98 | pub open_orders: SerumDexOpenOrders<'a, 'b>, 99 | pub target_orders: &'a AccountInfo<'b>, 100 | pub pool_token_coin: TokenAccount<'a, 'b>, 101 | pub pool_token_pc: TokenAccount<'a, 'b>, 102 | pub serum_dex_program_id: &'a AccountInfo<'b>, 103 | pub serum_market: SerumDexMarket<'a, 'b>, 104 | pub bids: &'a AccountInfo<'b>, 105 | pub asks: &'a AccountInfo<'b>, 106 | pub event_q: &'a AccountInfo<'b>, 107 | pub coin_vault: TokenAccount<'a, 'b>, 108 | pub pc_vault: TokenAccount<'a, 'b>, 109 | pub vault_signer: &'a AccountInfo<'b>, 110 | pub program_id: &'a AccountInfo<'b>, 111 | } 112 | 113 | impl<'a, 'b: 'a> RaydiumSwapArgs<'a, 'b> { 114 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 115 | const MIN_ACCOUNTS: usize = 15; 116 | if accounts.len() != MIN_ACCOUNTS { 117 | return Err(ProtocolError::InvalidAccountsLength); 118 | } 119 | let &[ 120 | ref amm_info_acc, 121 | ref authority, 122 | ref open_orders_acc, 123 | ref target_orders_acc, 124 | ref pool_token_coin_acc, 125 | ref pool_token_pc_acc, 126 | ref serum_dex_program_id, 127 | ref serum_market_acc, 128 | ref bids, 129 | ref asks, 130 | ref event_q, 131 | ref coin_vault_acc, 132 | ref pc_vault_acc, 133 | ref vault_signer, 134 | ref program_id, 135 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 136 | 137 | if !amm_info_acc.is_writable { 138 | return Err(ProtocolError::ReadonlyAccount); 139 | } 140 | let amm_info = RaydiumAmmInfo::new(amm_info_acc)?; 141 | 142 | if amm_info.token_coin()? != *pool_token_coin_acc.key { 143 | return Err(ProtocolError::InvalidTokenAccount); 144 | } 145 | if amm_info.token_pc()? != *pool_token_pc_acc.key { 146 | return Err(ProtocolError::InvalidTokenAccount); 147 | } 148 | if amm_info.open_orders()? != *open_orders_acc.key { 149 | return Err(ProtocolError::InvalidRaydiumAmmInfoAccount); 150 | } 151 | if amm_info.market()? != *serum_market_acc.key { 152 | return Err(ProtocolError::InvalidRaydiumAmmInfoAccount); 153 | } 154 | if !open_orders_acc.is_writable { 155 | return Err(ProtocolError::ReadonlyAccount); 156 | } 157 | if amm_info.serum_dex()? != *serum_dex_program_id.key { 158 | return Err(ProtocolError::InvalidSerumDexProgramId); 159 | } 160 | 161 | let market = SerumDexMarket::new(serum_market_acc)?; 162 | if *market.inner().owner != *serum_dex_program_id.key { 163 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 164 | } 165 | if *bids.owner != *serum_dex_program_id.key { 166 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 167 | } 168 | if *asks.owner != *serum_dex_program_id.key { 169 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 170 | } 171 | if *event_q.owner != *serum_dex_program_id.key { 172 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 173 | } 174 | Ok(Self { 175 | amm_info, 176 | authority, 177 | open_orders: SerumDexOpenOrders::new(open_orders_acc)?, 178 | target_orders: target_orders_acc, 179 | pool_token_coin: TokenAccount::new(pool_token_coin_acc)?, 180 | pool_token_pc: TokenAccount::new(pool_token_pc_acc)?, 181 | serum_dex_program_id, 182 | serum_market: market, 183 | bids, 184 | asks, 185 | event_q, 186 | coin_vault: TokenAccount::new(coin_vault_acc)?, 187 | pc_vault: TokenAccount::new(pc_vault_acc)?, 188 | vault_signer, 189 | program_id, 190 | }) 191 | } 192 | 193 | // pub fn find_token_pair( 194 | // &self, 195 | // source_token_account_mint: &Pubkey, 196 | // ) -> ProtocolResult<(&TokenAccount<'a, 'b>, &TokenAccount<'a, 'b>)> { 197 | // if *source_token_account_mint == self.token_a.mint()? { 198 | // Ok((&self.token_a, &self.token_b)) 199 | // } else { 200 | // Ok((&self.token_b, &self.token_a)) 201 | // } 202 | // } 203 | } 204 | 205 | #[derive(Copy, Clone)] 206 | pub struct RaydiumSwapArgs2<'a, 'b: 'a> { 207 | pub amm_info: RaydiumAmmInfo<'a, 'b>, 208 | pub authority: &'a AccountInfo<'b>, 209 | pub open_orders: SerumDexOpenOrders<'a, 'b>, 210 | pub pool_token_coin: TokenAccount<'a, 'b>, 211 | pub pool_token_pc: TokenAccount<'a, 'b>, 212 | pub serum_dex_program_id: &'a AccountInfo<'b>, 213 | pub serum_market: SerumDexMarket<'a, 'b>, 214 | pub bids: &'a AccountInfo<'b>, 215 | pub asks: &'a AccountInfo<'b>, 216 | pub event_q: &'a AccountInfo<'b>, 217 | pub coin_vault: TokenAccount<'a, 'b>, 218 | pub pc_vault: TokenAccount<'a, 'b>, 219 | pub vault_signer: &'a AccountInfo<'b>, 220 | pub program_id: &'a AccountInfo<'b>, 221 | } 222 | 223 | impl<'a, 'b: 'a> RaydiumSwapArgs2<'a, 'b> { 224 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 225 | const MIN_ACCOUNTS: usize = 14; 226 | if accounts.len() != MIN_ACCOUNTS { 227 | return Err(ProtocolError::InvalidAccountsLength); 228 | } 229 | let &[ 230 | ref amm_info_acc, 231 | ref authority, 232 | ref open_orders_acc, 233 | ref pool_token_coin_acc, 234 | ref pool_token_pc_acc, 235 | ref serum_dex_program_id, 236 | ref serum_market_acc, 237 | ref bids, 238 | ref asks, 239 | ref event_q, 240 | ref coin_vault_acc, 241 | ref pc_vault_acc, 242 | ref vault_signer, 243 | ref program_id, 244 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 245 | 246 | if !amm_info_acc.is_writable { 247 | return Err(ProtocolError::ReadonlyAccount); 248 | } 249 | let amm_info = RaydiumAmmInfo::new(amm_info_acc)?; 250 | 251 | if amm_info.token_coin()? != *pool_token_coin_acc.key { 252 | return Err(ProtocolError::InvalidTokenAccount); 253 | } 254 | if amm_info.token_pc()? != *pool_token_pc_acc.key { 255 | return Err(ProtocolError::InvalidTokenAccount); 256 | } 257 | if amm_info.open_orders()? != *open_orders_acc.key { 258 | return Err(ProtocolError::InvalidRaydiumAmmInfoAccount); 259 | } 260 | if amm_info.market()? != *serum_market_acc.key { 261 | return Err(ProtocolError::InvalidRaydiumAmmInfoAccount); 262 | } 263 | if !open_orders_acc.is_writable { 264 | return Err(ProtocolError::ReadonlyAccount); 265 | } 266 | if amm_info.serum_dex()? != *serum_dex_program_id.key { 267 | return Err(ProtocolError::InvalidSerumDexProgramId); 268 | } 269 | 270 | let market = SerumDexMarket::new(serum_market_acc)?; 271 | if *market.inner().owner != *serum_dex_program_id.key { 272 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 273 | } 274 | if *bids.owner != *serum_dex_program_id.key { 275 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 276 | } 277 | if *asks.owner != *serum_dex_program_id.key { 278 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 279 | } 280 | if *event_q.owner != *serum_dex_program_id.key { 281 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 282 | } 283 | Ok(Self { 284 | amm_info, 285 | authority, 286 | open_orders: SerumDexOpenOrders::new(open_orders_acc)?, 287 | pool_token_coin: TokenAccount::new(pool_token_coin_acc)?, 288 | pool_token_pc: TokenAccount::new(pool_token_pc_acc)?, 289 | serum_dex_program_id, 290 | serum_market: market, 291 | bids, 292 | asks, 293 | event_q, 294 | coin_vault: TokenAccount::new(coin_vault_acc)?, 295 | pc_vault: TokenAccount::new(pc_vault_acc)?, 296 | vault_signer, 297 | program_id, 298 | }) 299 | } 300 | 301 | // pub fn find_token_pair( 302 | // &self, 303 | // source_token_account_mint: &Pubkey, 304 | // ) -> ProtocolResult<(&TokenAccount<'a, 'b>, &TokenAccount<'a, 'b>)> { 305 | // if *source_token_account_mint == self.token_a.mint()? { 306 | // Ok((&self.token_a, &self.token_b)) 307 | // } else { 308 | // Ok((&self.token_b, &self.token_a)) 309 | // } 310 | // } 311 | } 312 | 313 | #[cfg(test)] 314 | mod tests { 315 | use super::*; 316 | use solana_sdk::{account_info::AccountInfo, pubkey::Pubkey}; 317 | use std::str::FromStr; 318 | 319 | #[test] 320 | fn test_raydium_struct() { 321 | let raydium_program_id = 322 | Pubkey::from_str("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8").unwrap(); 323 | let raydium_pubkey = Pubkey::from_str("DVa7Qmb5ct9RCpaU7UTpSaf3GVMYz17vNVU67XpdCRut").unwrap(); 324 | let raydium_data = 325 | "Csa6r43w6Tksashc251QAkcpr6D4zyiWB4sSrw5xDZzoH9FsPfiZDXJSNMMTFHVsbKqVyDZb32anWxQN 326 | Nk9FL7bCpKPZ7qMdCe6eCkjjRbbdiYvHBV1TrhWWwQ6pKP3rNVfae2R25Hj8ttD9CwVTz2CRzcDDdu88N5T6J67xVhcBKwEmJB3i 327 | txbnWWnvHf95TBXbmmAZFrbfPm6153Re8mjTUVswfNCRVC2ypRV8jzZoBbohMWrbPxKW4VXZdaEE8JwVU5QrPFvKFJKkmeReiBre 328 | b7Huy52gGioSCu8FLWg8JYQHMzgnr31tR5sDa1WSVJVPUQ4t4rRazqcdALsdSKZHUrnZACbLTsEgiXQWn4Ncc9eVciH78oQsXgvP 329 | sWC4qSURfyQZoe7QUZ5pb6YtY5A4YASwim5JauPHVGdd6sLFTea3DK7RUdmpDcmyKbnQKBVE3mTMA6useCSrUtHChwpETDkTC1gh 330 | EQtZQTVdefcPsAGLXEy3LioEqfnny3huwYxuTnT6LYt7KYP1FqqRoff7zQUvWn8xRq45pxWjbm3HLGimno7tCWYVRUwMH74vDfgg 331 | 7AebDUTdRA72GhBUG1Y2852URSs3crQ4qDs9z62AS2ymyMZ8Qicz9RmimyU9iCU8n96pZ7Y57XKydcW8aDKF1gBi3bdLDGyUAdYY 332 | b51Jijykz38oM6KPswC7rAxgTVVgiMu4JvKmVwecn7NCP4iWoM9k8vrYaa8tS3VBZtAMCkVtuwpQeYVZ9HPZkwVPV9o6oFXBidkZ 333 | aQukNQ7sfZSCEGj6vKv4fGJNpuDJDZiUXhveEjnbYffrm5Gnfz2kvSSdCgotWNJwcJZkfv5LsMkprfTXodEXXnLqqHj3LM8tNSFu 334 | CqhMRFKbuHdZt1EfvFWcyxNukAhUXZn5k4MVNQdhQZ5poqMfUa6AzgXBMVAYCoFrsKF9qHbCEHFLNcznS3J3go3xcCnigQtQEctX 335 | awtxg5yoJmS91iDZt2nTceatH7LN78fA5DxmJDn8kpF3F2"; 336 | let mut raydium_data = bs58::decode(raydium_data.replace('\n', "")) 337 | .into_vec() 338 | .unwrap(); 339 | let mut raydium_lamports = 6124800u64; 340 | let raydium_account_info = AccountInfo::new( 341 | &raydium_pubkey, 342 | false, 343 | true, 344 | &mut raydium_lamports, 345 | &mut raydium_data[..], 346 | &raydium_program_id, 347 | false, 348 | 248, 349 | ); 350 | let raydium_info = RaydiumAmmInfo::new(&raydium_account_info).unwrap(); 351 | assert_eq!(*raydium_info.pubkey(), raydium_pubkey); 352 | assert_eq!(*raydium_info.inner().owner, raydium_program_id); 353 | assert_eq!( 354 | raydium_info.open_orders().unwrap().to_string(), 355 | "7UF3m8hDGZ6bNnHzaT2YHrhp7A7n9qFfBj6QEpHPv5S8" 356 | ); 357 | assert_eq!( 358 | raydium_info.market().unwrap().to_string(), 359 | "teE55QrL4a4QSfydR9dnHF97jgCfptpuigbb53Lo95g" 360 | ); 361 | assert_eq!( 362 | raydium_info.token_coin().unwrap().to_string(), 363 | "3wqhzSB9avepM9xMteiZnbJw75zmTBDVmPFLTQAGcSMN" 364 | ); 365 | assert_eq!( 366 | raydium_info.token_pc().unwrap().to_string(), 367 | "5GtSbKJEPaoumrDzNj4kGkgZtfDyUceKaHrPziazALC1" 368 | ); 369 | assert_eq!( 370 | raydium_info.coin_mint().unwrap().to_string(), 371 | "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" 372 | ); 373 | assert_eq!( 374 | raydium_info.pc_mint().unwrap().to_string(), 375 | "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" 376 | ); 377 | assert_eq!( 378 | raydium_info.serum_dex().unwrap().to_string(), 379 | "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" 380 | ); 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/serum_dex.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | declare_validated_account_wrapper, 3 | error::{ProtocolError, ProtocolResult}, 4 | exchanger::serum_dex::matching::Side as DexSide, 5 | parser::base::TokenAccount, 6 | }; 7 | use arrayref::{array_ref, array_refs}; 8 | use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; 9 | 10 | declare_validated_account_wrapper!(SerumDexMarket, |account: &AccountInfo| { 11 | if !account.is_writable { 12 | return Err(ProtocolError::ReadonlyAccount); 13 | } 14 | let data = account 15 | .try_borrow_data() 16 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 17 | // [5,8,32,8,32,32,32,8,8,32,8,8,8,32,32,32,32,8,8,8,8,7] 18 | const MARKET_LEN: usize = 388; 19 | if data.len() != MARKET_LEN { 20 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 21 | } 22 | let flag_data = u64::from_le_bytes(*array_ref![data, 5, 8]); 23 | /** 24 | * Initialized = 1u64 << 0, 25 | * Market = 1u64 << 1, 26 | */ 27 | if flag_data != 3 { 28 | // if flag_data != (SerumAccountFlag::Initialized | SerumAccountFlag::Market).bits() { 29 | msg!("flag_data: {:?}, expect: {:?}", flag_data, 3,); 30 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 31 | } 32 | Ok(()) 33 | }); 34 | 35 | declare_validated_account_wrapper!(SerumDexOpenOrders, |account: &AccountInfo| { 36 | if !account.is_writable { 37 | return Err(ProtocolError::ReadonlyAccount); 38 | } 39 | let account_data = account 40 | .try_borrow_data() 41 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 42 | // [5,8,32,32,8,8,8,8,16,16,16*128,8*128,8,7] 43 | const MARKET_LEN: usize = 3228; 44 | if account_data.len() != MARKET_LEN { 45 | return Err(ProtocolError::InvalidSerumDexMarketAccount); 46 | } 47 | #[allow(clippy::ptr_offset_with_cast)] 48 | let (_, data, _) = array_refs![&account_data, 5; ..; 7]; 49 | let flag_data = u64::from_le_bytes(*array_ref![data, 0, 8]); 50 | /** 51 | * Initialized = 1u64 << 0, 52 | * Market = 1u64 << 1, 53 | */ 54 | // BitFlags:: 55 | if flag_data != 5 { 56 | // if flag_data != (SerumAccountFlag::Initialized | SerumAccountFlag::OpenOrders).bits() { 57 | msg!("flag_data: {:?}, expect: {:?}", flag_data, 5,); 58 | return Err(ProtocolError::InvalidOpenOrdersAccount); 59 | } 60 | Ok(()) 61 | }); 62 | 63 | #[allow(unused)] 64 | impl<'a, 'b: 'a> SerumDexMarket<'a, 'b> { 65 | pub fn coin_mint(self) -> ProtocolResult { 66 | let account_data = self 67 | .inner() 68 | .try_borrow_data() 69 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 70 | #[allow(clippy::ptr_offset_with_cast)] 71 | let (_, data, _) = array_refs![&account_data, 5; ..; 7]; 72 | Ok(Pubkey::new_from_array(*array_ref![data, 48, 32])) 73 | } 74 | 75 | pub fn pc_mint(self) -> ProtocolResult { 76 | let account_data = self 77 | .inner() 78 | .try_borrow_data() 79 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 80 | #[allow(clippy::ptr_offset_with_cast)] 81 | let (_, data, _) = array_refs![&account_data, 5; ..; 7]; 82 | Ok(Pubkey::new_from_array(*array_ref![data, 80, 32])) 83 | } 84 | } 85 | 86 | #[allow(unused)] 87 | impl<'a, 'b: 'a> SerumDexOpenOrders<'a, 'b> { 88 | pub fn market(self) -> ProtocolResult { 89 | let account_data = self 90 | .inner() 91 | .try_borrow_data() 92 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 93 | #[allow(clippy::ptr_offset_with_cast)] 94 | let (_, data, _) = array_refs![&account_data, 5; ..; 7]; 95 | Ok(Pubkey::new_from_array(*array_ref![data, 8, 32])) 96 | } 97 | 98 | pub fn owner(self) -> ProtocolResult { 99 | let account_data = self 100 | .inner() 101 | .try_borrow_data() 102 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 103 | #[allow(clippy::ptr_offset_with_cast)] 104 | let (_, data, _) = array_refs![&account_data, 5; ..; 7]; 105 | Ok(Pubkey::new_from_array(*array_ref![data, 40, 32])) 106 | } 107 | } 108 | 109 | #[derive(Copy, Clone)] 110 | pub struct SerumDexArgs<'a, 'b: 'a> { 111 | pub open_orders: SerumDexOpenOrders<'a, 'b>, 112 | pub market: SerumDexMarket<'a, 'b>, 113 | pub request_queue_acc: &'a AccountInfo<'b>, 114 | pub event_queue_acc: &'a AccountInfo<'b>, 115 | pub bids_acc: &'a AccountInfo<'b>, 116 | pub asks_acc: &'a AccountInfo<'b>, 117 | pub coin_vault_acc: TokenAccount<'a, 'b>, 118 | pub pc_vault_acc: TokenAccount<'a, 'b>, 119 | pub vault_signer_acc: &'a AccountInfo<'b>, 120 | pub rent_sysvar_acc: &'a AccountInfo<'b>, 121 | pub program_acc: &'a AccountInfo<'b>, 122 | } 123 | 124 | impl<'a, 'b: 'a> SerumDexArgs<'a, 'b> { 125 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 126 | const MIN_ACCOUNTS: usize = 11; 127 | if accounts.len() != MIN_ACCOUNTS { 128 | return Err(ProtocolError::InvalidAccountsLength); 129 | } 130 | let &[ 131 | ref open_orders_acc, 132 | ref market_acc, 133 | ref request_queue_acc, 134 | ref event_queue_acc, 135 | ref bids_acc, 136 | ref asks_acc, 137 | ref coin_vault_acc, 138 | ref pc_vault_acc, 139 | ref vault_signer_acc, 140 | ref rent_sysvar_acc, 141 | ref serum_program_acc, 142 | ]: &'a[AccountInfo<'b>; MIN_ACCOUNTS] = array_ref![accounts, 0, MIN_ACCOUNTS]; 143 | 144 | let market = SerumDexMarket::new(market_acc)?; 145 | if *market.inner().owner != *serum_program_acc.key { 146 | return Err(ProtocolError::InvalidProgramAddress); 147 | } 148 | let open_orders = SerumDexOpenOrders::new(open_orders_acc)?; 149 | if *open_orders.inner().owner != *serum_program_acc.key { 150 | return Err(ProtocolError::InvalidProgramAddress); 151 | } 152 | // if open_orders.market()? != *market.pubkey() { 153 | // return Err(ProtocolError::InvalidSerumDexMarketAccount); 154 | // } 155 | 156 | Ok(SerumDexArgs { 157 | open_orders, 158 | market, 159 | request_queue_acc, 160 | event_queue_acc, 161 | bids_acc, 162 | asks_acc, 163 | coin_vault_acc: TokenAccount::new(coin_vault_acc)?, 164 | pc_vault_acc: TokenAccount::new(pc_vault_acc)?, 165 | vault_signer_acc, 166 | rent_sysvar_acc, 167 | program_acc: serum_program_acc, 168 | }) 169 | } 170 | 171 | pub fn find_side(&self, source_mint: &Pubkey) -> ProtocolResult { 172 | if *source_mint == self.coin_vault_acc.mint()? { 173 | Ok(DexSide::Ask) 174 | } else { 175 | Ok(DexSide::Bid) 176 | } 177 | } 178 | 179 | // pub fn check_open_orders_owner(&self, target: &Pubkey) -> ProtocolResult<()> { 180 | // if self.open_orders.owner()? != *target { 181 | // return Err(ProtocolError::InvalidOpenOrdersAccount); 182 | // } 183 | // Ok(()) 184 | // } 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use super::*; 190 | use solana_sdk::{account_info::AccountInfo, pubkey::Pubkey}; 191 | use std::str::FromStr; 192 | 193 | #[test] 194 | fn test_serum_dex_market() { 195 | let market_data = r#"GmH4gu6PYUUKDZqX8AT2ZH7MKQkqEiK1rkgus44yrCJvP7UDfLpQzbFKzfg 196 | Ux1oSffopN2NGno33fnjhD37awk2MPJrXgRiQjwQWWwspgrrjXVKhP87vynWu4FzjGgx8USsnBa5 197 | mNEZb2rKvNmVZKekzZUpdSAiXEMbVvEpAn1tQTderQCh69t84sPfcVfseAPEKyJYcAiFLCTrKFmQ3 198 | SVQiartpqiySprqLqkqto5Z3LAVRGBvVvcinYuZBN49ZbBaMGxXS9wt6tXN8ZqmoZMfYvc3un68Du 199 | J5vyRPyiYz56LqovWnbjjXY76rRPzsbXR3EqYNMyCFjoqxnsH3LLJVYXwT11ggvUery3J8bhDbdvS 200 | JaacCyTEuaMuWXjJMcsBxW2NQLAPzasX8vu1uTDjqnvCkZKhYcGtCpiLddLQEMXu6mTEE6ZmT73rH 201 | CLaoGKPSYxuVkunGb4AtkU4mSUfWw3EbKc6s6sEvgi5Ec47RYGdNDMK31jENakYtSAweGRSin1iB7 202 | G11FU1xhNE"#; 203 | let mut data = bs58::decode(market_data.replace('\n', "")) 204 | .into_vec() 205 | .unwrap(); 206 | let pubkey = Pubkey::from_str("9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT").unwrap(); 207 | let owner = Pubkey::from_str("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin").unwrap(); 208 | let mut lamports = 1003591360u64; 209 | let account_info = AccountInfo::new( 210 | &pubkey, 211 | false, 212 | true, 213 | &mut lamports, 214 | &mut data[..], 215 | &owner, 216 | false, 217 | 246, 218 | ); 219 | let market = SerumDexMarket::new(&account_info).unwrap(); 220 | let expect_coin_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(); 221 | let expect_pc_mint = Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap(); 222 | assert_eq!(market.coin_mint().unwrap(), expect_coin_mint); 223 | assert_eq!(market.pc_mint().unwrap(), expect_pc_mint); 224 | } 225 | 226 | #[test] 227 | fn test_serum_dex_open_orders() { 228 | let market_data = r#"2q2DvF2TVYmHA4NVBRjCtHoK3PWh7AztLUhBKnMGd6DJJZNattYP8joN5Lwm 229 | kM6Mqf1jcfSCo6QTnvL1F1qdg19dLbbVw3hJCHVQ1GMaWfNaZQuYxRGNwuaJhyBYhAN7pt 230 | FhJgMpffWZSg79HXCq3Pfh4aCShtcPM11Kg7mPam1PKEHAHLVVmbawn2BbnG39xUgRQxQ5 231 | vDRYzEpEoBzEv6QrkdffdxogAhpSFF1PkL5mXTLfv4qyq4AnE9rjcDHJ3nyXoNFrnH4SDk 232 | dmWMAmhoY2po17hWjVK7tFyrR9R6zKtrfX3xM72VkjPNJLqhQBxWpTTbS313L3csiTaPNw 233 | TYoVARVu4gCuzXgUfLFh2oKssuM2ccH8yR3DDVUADr9P2P61u8TMSFCXpGje4X5dpw1eGM 234 | j782tiKam6QWFuYnC8CqpEXuDdhmzFkqycJ53TuNkDWjvDPbVQMpySQtsTW4tNTFHu3TNL 235 | kNuHdqYpzG2iZPoAoHBMcjqTagDpuMTkgvrNZn3wRewjVhmGd7MnbyvmZTdY6j8Ps7jSbN 236 | qRpADVxTwQ7Nb55YnLUzeVGwi1s12q1q1F7tZDWXPEyyWjzhSHjFYZURPajLHnAu8Qp2a1 237 | 2T6ZsCfbCWpkrYqvikEEMHuTnpqtfRfdCu6D2zagQYhQu2Nwa9gge2vLgfcomvFYZ4Sfo9 238 | 9cRq87havVCorA3QCwqL9Y5byEawdFaQJLrjmLznFBRcjVrnMmcJZbHpWtVNHggjYf5A7i 239 | rXSDW6M9CN8CF7v7eZLSeKjypTVLb7HUipMaycwkSLyJ496jqQVn7oojCeEZvgqr6BMQgC 240 | 3tFRBq84AKGW6yrLAU9FmxUktYmvUDBiq9nzxLY74FSXfatCgVNdGagdg3sKyxVy7YNkaH 241 | D5q9pf9Y5n6rnDNNoraus1ABpUUKAkAwWhRrZqrtygvCJTern1XLU3JEZjH9uaMA24MkqT 242 | ze7GfvunQiNDyRJ4RTgeLD8GGanvRv24TJJQ4MxRQbjSBgyz6vUB5mMPao4w8rNkBiLZdQ 243 | sz3WfZ4aUs9m3yhaXunhWAdDRrTBmjfRosb4NSKSLBxsL6RTBvLUoRYHetaiZtptNNkviH 244 | MkJ5zuxhZxyr3V1MGC7GsLBkQTnrd71o4yu5cQcwpVxgqHCxJ19Z8ffCpD4FrHZfYtuiXs 245 | z6Ar1ahDQqNKrzDukmkjQh1ZdSDiQBCLAiy5SGRymDP3LfeqPScaftDWnLLWkNAdhfnKQG 246 | SumyMQhyCm52WKVVW7qdSSp78nztpapxkcPJ8ZGh6Ta69H1LjQhyjerSk4VCcTTmWWZEg1 247 | LpVVnjWeqErBVcdpVnWUBcWPEvc8hfJXbrMuXp1aXNkce5fF8Uw3gJrCGGcxuCoxbS9HKD 248 | EiZ64GQQVEiY6zFg9aENXheXQs2fubQDYx6NVj2rmNjTnyVoQYYsjNktrJWmBEuQjztxoP 249 | PaxFWST7bQQAx1g3rGt3AJk79vMSRSy3mmLYDUPens8h8pSzQUKzWpHQsNjtnDpexrtrYs 250 | Yf4abKtRFRvF9VxQ4F2bpQTUhcix72G5qrHx3eHKrLxY8Yjf9cyzRpmRsqQrJsP1C8ZVFU 251 | WiqiQ8WhhxZkampy697URHHSAwB3z3UaBGRa4o9ndwFj9gP7x4RQaQTi8ymb5bHqSnQWQg 252 | sugkjTpWBT6fmuk4Df7HwCbDWygme6ayd5tttQkg6UGPacgi2aACkRr3MZPcF9ZFH2LtCT 253 | 9Hdq8ry3Gju3BbrUBqDfp34EoeUtKkVr8DHy1kQbwS82Bwn3cfiASx8YJGmBuLTZ56zfvF 254 | FuRe3dChFsaq8ZAj9ivZKsnXV4SQsu7QFWpCRkv9wPoDCLkhvxb2gD2vJmigxwcU7hcn1a 255 | zhtLRd9fJqQEYkC3Lw5ykjCPkSvo7W9nNBtTR5npjh1n8pZyKCpLQrgwxqorGzA73ytPLp 256 | gkKUDiCo5YnUCKds8Co4JsX7i8fAunCgV4SnRAtQNufaPbouuyPXG35v3EKC8AgnhFzw8n 257 | j9ongtBPEcrSZCWF9YSg1vfM6c6hMqgCuwiymxXAbMjPKvmurGNKSE6Liy34v9YfrVcyMp 258 | TShT9hFikuNqgHCjZuLwdDRPPiHasaCpcwWDdkwpmTVPAxihbikVFaQpqAXr7aRHUVrLZx 259 | GexG4bi4w9pvxwAsYzXsJZrqrRmMUu1JSJXwRADpcVefj8hofSZ1PmWXP1vusKByFgvvNa 260 | gwJaPkv1uEoSCFqt447HvQPRSqmHUGrfau8zoMLnAh3jKiTN44FPJn4ZpJvEz8mi1GNnbM 261 | MMvHqZoRGTp29p8AAYRmgRe1SbqASEWheCwQP5naxzcLPKKXofzWdpC2NqjRf1BW36nLPk 262 | hcSwt1GKXFnbMV8zggEByyntiHHfz6okCgewwnMmcaFkXhm4mweqF9JFxa4msSXkBzWtSB 263 | BpXuHRktCrN62LuM5BicadiRymfwpYo9mjTDP91gXtNknEPecp92nVt22i1QwjSdctebqi 264 | M9g2NLmoCwxjPbWXYKfRM81xeXWdXsQ6BCy7aKeDYoD5XHuBLxxLfBiCy2WHKXmbUCBo8Q 265 | S7L4EhcMUM6LQv3GRqBcfTrKEAbSFNm6jHHx2rAJbSuRgEwDDnZ5xkm5DcLEGpurNvH8Vr 266 | KVe977tUx2DnirQU8tvi7P94w2vyw7CYwyNKmQnWPJfX7Bp4MzyL2nP89XDicSReu4vyuo 267 | Qv5Dt5Jg3CzznayLrGdp9g1Lud46CgHcdUgJGaKZvV682TW2CDXDWXMTUcwQSt7VR5jssJ 268 | Q9J8P3P2miU5tpXeyExeR7XKcSdiCqDCT7Wh1pR8bw8WWfaRKpdJkVUMmonYbLj58qmffr 269 | Wg9R1AAWdgaL1j7j5uKgC13ben4i36xkEPKSqo2mDYFb5MXp8NRmi7goZwrztZLfS5YN1S 270 | UFXfFZE4HeBtC37vVtu1aEgJmMLKPigRRVKRetRjGbahuP5Lcmnt5q8Wgwf2cqHuKaUEeb 271 | WuKksQRPCZasPRYtgztWrjvjWpHrkJnkkMF9664shPyDg1rn2U3CTTa7zwiUVQia9emTft 272 | Q3b9uJETZ2YneyRCyyu5xaUtvLpZjmppi3UuLTKTRUoidQtxaSPk6DyreDNyrT9PqzfJUZ 273 | J7qtsefKPpJMEL8sC9WPDmhHQwkHHSpxog9Q1ZhmT9zSiFs4w7ZEws6KQTxGcvQCYHcC9V 274 | 92WdQYkGuc9ZUZW8nkrEeYJ1oyggdm9dVsiCGnwN1yKfh7okH1Sd52vTqWhaRhR53fpreQ 275 | r6U9QJVcSU4dGEipQAwWogmQ6KE8E8QzZ1GV1NXbnRbKQwuFertqAjXutaDv9Sa2qw1KNp 276 | 4F9AYJjw4qQhaGRRxPFMSW2m6rk73fSACkVVzeGgRbSqNzB674KmZwcmG8dTQEzcGDF8FR 277 | JPRbJEf4r6xkX1oBScAcarAJSBKPcefom8EAKFHu4oNgpCcYPaoPaZBD7e79WqVXTDGmE5 278 | aDu9tLbqFjA78LFzEDStitebMc6tBmJ8pHhuJqEuX3bb31Pp4RXpDdDudJq2PbBQXRyupu 279 | UQfeamq3E8ovob1jHiS76Pk4capg4ERMZgZsEB2TUnd1gmMmcYBBJNExacCS9SzY8MzpjH 280 | vjBpNDo4B5qaxF86YTHbYcwny5cpHwGBrCc63rgrQELWyLbB5dZotyPyARc6kW6yVkgcwH 281 | 31v5HBC9WzRgvYCyQR1qmQ2GZ6Jq8CH6RdPNqJYtbQLDJRH448jghVeuFgpc2zn1PGci1a 282 | uo5c9o1ZcRFfEXiua75q8Yiigir1ir9G73NMaK6oah8owGYkcMzcidAbfbv96wn7i7KmdP 283 | h4V4BRqyqCPVZyqFd87WGndFC9TwSwtzJa6iNZQguRWefwcXeDif5dpTUXTYwvFpLTaTHN 284 | ryrQrf71od7Qx59wGsKNZQgZwEJkAWM8D6aypRQ68dTNKPRXJ3C84m2QNYwfLotrYEzyNy 285 | 2SCVwxwRuDAF6CAhiaME5HJEdnKBumCRgcZ5e9i8LfzQcM2hVfxu1ZK6vnkiU7d1YCpMCC 286 | nVkt4VCkcpdn4mkHVDVY81TNdSLAwmGtbdWmACgPVC4mVAi6y5kPx57YPUKiW6Y2fiCCxExZk2Lyutqy 287 | PFGfo6xZEm3351m6b6GRhAxPFkYbateh9s8xcNWVqTLXBSS8jsUx8BeWu2i4SVyxoLVBgJhVGURaX3Rz 288 | avKkeh6Nn313MU7gefoEda4quR2VaGjJGMqQaoe7SYAd93pZYbaKpEA7pvX5Jk8WQQaQtA6dG7824vAN 289 | DpQDTnGr57YavqpLq9Yi9HCzDzLSpd27HKWGFbrbr5zHPCu5FccLNHrLHYQkAAobowfiEvBb91Rcc3Dj 290 | UhNFaoyqJ7aZm14QZS9c9FHesiGEqUFNiCZfkWz"#; 291 | let mut data = bs58::decode(market_data.replace('\n', "")) 292 | .into_vec() 293 | .unwrap(); 294 | let pubkey = Pubkey::from_str("HRk9CMrpq7Jn9sh7mzxE8CChHG8dneX9p475QKz4Fsfc").unwrap(); 295 | let owner = Pubkey::from_str("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin").unwrap(); 296 | let mut lamports = 1003591360u64; 297 | let account_info = AccountInfo::new( 298 | &pubkey, 299 | false, 300 | true, 301 | &mut lamports, 302 | &mut data[..], 303 | &owner, 304 | false, 305 | 246, 306 | ); 307 | let open_orders = SerumDexOpenOrders::new(&account_info).unwrap(); 308 | let expect_market = Pubkey::from_str("9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT").unwrap(); 309 | let expect_owner = Pubkey::from_str("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1").unwrap(); 310 | assert_eq!(open_orders.market().unwrap(), expect_market); 311 | assert_eq!(open_orders.owner().unwrap(), expect_owner); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/spl_token_swap.rs: -------------------------------------------------------------------------------- 1 | use arrayref::array_refs; 2 | use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; 3 | 4 | use crate::{ 5 | check_unreachable, declare_validated_account_wrapper, 6 | error::{ProtocolError, ProtocolResult}, 7 | }; 8 | 9 | use super::base::{TokenAccount, TokenMint}; 10 | 11 | declare_validated_account_wrapper!(SplTokenSwapInfo, |account: &AccountInfo| { 12 | let data = account 13 | .try_borrow_data() 14 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 15 | if data.len() != 324 { 16 | msg!( 17 | "spl-tokenswap-info, data.len(): {}, is_initialized: {}", 18 | data.len(), 19 | data[1] 20 | ); 21 | return Err(ProtocolError::InvalidSplTokenSwapInfoAccount); 22 | } 23 | let version = data[0]; 24 | if version != 1u8 { 25 | msg!("spl-tokenswap-info, version: {}", data[0]); 26 | return Err(ProtocolError::InvalidSplTokenSwapInfoAccount); 27 | } 28 | let is_initialized = data[1]; 29 | if is_initialized != 1u8 { 30 | msg!( 31 | "spl-tokenswap-info, data.len(): {}, is_initialized: {}", 32 | data.len(), 33 | data[1] 34 | ); 35 | return Err(ProtocolError::InvalidSplTokenSwapInfoAccount); 36 | } 37 | Ok(()) 38 | }); 39 | 40 | impl<'a, 'b: 'a> SplTokenSwapInfo<'a, 'b> {} 41 | 42 | #[derive(Copy, Clone)] 43 | pub struct SplTokenSwapArgs<'a, 'b: 'a> { 44 | pub swap_info: SplTokenSwapInfo<'a, 'b>, 45 | pub authority_acc_info: &'a AccountInfo<'b>, 46 | pub token_a_account: TokenAccount<'a, 'b>, 47 | pub token_b_account: TokenAccount<'a, 'b>, 48 | pub pool_mint: TokenMint<'a, 'b>, 49 | pub fee_account: TokenAccount<'a, 'b>, 50 | pub program: &'a AccountInfo<'b>, 51 | pub host_fee_account: Option>, 52 | } 53 | 54 | impl<'a, 'b: 'a> SplTokenSwapArgs<'a, 'b> { 55 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 56 | const MIN_ACCOUNTS: usize = 7; 57 | if !(accounts.len() == MIN_ACCOUNTS || accounts.len() == MIN_ACCOUNTS + 1) { 58 | return Err(ProtocolError::InvalidAccountsLength); 59 | } 60 | #[allow(clippy::ptr_offset_with_cast)] 61 | let (fixed_accounts, host_fee_account): ( 62 | &'a [AccountInfo<'b>; MIN_ACCOUNTS], 63 | &'a [AccountInfo<'b>], 64 | ) = array_refs![accounts, MIN_ACCOUNTS; .. ;]; 65 | let &[ 66 | ref swap_info_acc, 67 | ref authority_acc, 68 | ref token_a_acc, 69 | ref token_b_acc, 70 | ref pool_mint_acc, 71 | ref fee_acc, 72 | ref program_acc, 73 | ]: &'a [AccountInfo<'b>; MIN_ACCOUNTS] = fixed_accounts; 74 | let host_fee_acc = match host_fee_account { 75 | [] => None, 76 | [ref acc] => Some(TokenAccount::new(acc)?), 77 | _ => check_unreachable!()?, 78 | }; 79 | let swap_info = SplTokenSwapInfo::new(swap_info_acc)?; 80 | if *swap_info.inner().owner != *program_acc.key { 81 | return Err(ProtocolError::InvalidProgramAddress); 82 | } 83 | // other checks will run in spl-token-swap 84 | Ok(SplTokenSwapArgs { 85 | swap_info, 86 | authority_acc_info: authority_acc, 87 | token_a_account: TokenAccount::new(token_a_acc)?, 88 | token_b_account: TokenAccount::new(token_b_acc)?, 89 | pool_mint: TokenMint::new(pool_mint_acc)?, 90 | fee_account: TokenAccount::new(fee_acc)?, 91 | program: program_acc, 92 | host_fee_account: host_fee_acc, 93 | }) 94 | } 95 | 96 | pub fn find_token_pair( 97 | &self, 98 | source_token_account_mint: &Pubkey, 99 | ) -> ProtocolResult<(&TokenAccount<'a, 'b>, &TokenAccount<'a, 'b>)> { 100 | if *source_token_account_mint == self.token_a_account.mint()? { 101 | Ok((&self.token_a_account, &self.token_b_account)) 102 | } else { 103 | Ok((&self.token_b_account, &self.token_a_account)) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/program-rust/src/parser/stable_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | declare_validated_account_wrapper, 3 | error::{ProtocolError, ProtocolResult}, 4 | }; 5 | use arrayref::{array_ref, array_refs}; 6 | use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; 7 | 8 | use super::base::TokenAccount; 9 | 10 | declare_validated_account_wrapper!(StableSwapInfo, |account: &AccountInfo| { 11 | let data = account 12 | .try_borrow_data() 13 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 14 | if data.len() != 395 { 15 | return Err(ProtocolError::InvalidStableSwapAccount); 16 | } 17 | let is_initialized = data[0]; 18 | if is_initialized != 1u8 { 19 | return Err(ProtocolError::InvalidStableSwapAccountState); 20 | } 21 | let is_paused = data[1]; 22 | if is_paused == 1u8 { 23 | return Err(ProtocolError::InvalidStableSwapAccountState); 24 | } 25 | Ok(()) 26 | }); 27 | 28 | #[allow(dead_code)] 29 | impl<'a, 'b: 'a> StableSwapInfo<'a, 'b> { 30 | pub fn token_a(self) -> ProtocolResult { 31 | let data = self 32 | .inner() 33 | .try_borrow_data() 34 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 35 | Ok(Pubkey::new_from_array(*array_ref![data, 107, 32])) 36 | } 37 | 38 | pub fn token_b(self) -> ProtocolResult { 39 | let data = self 40 | .inner() 41 | .try_borrow_data() 42 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 43 | Ok(Pubkey::new_from_array(*array_ref![data, 139, 32])) 44 | } 45 | 46 | pub fn token_a_mint(self) -> ProtocolResult { 47 | let data = self 48 | .inner() 49 | .try_borrow_data() 50 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 51 | Ok(Pubkey::new_from_array(*array_ref![data, 203, 32])) 52 | } 53 | 54 | pub fn token_b_mint(self) -> ProtocolResult { 55 | let data = self 56 | .inner() 57 | .try_borrow_data() 58 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 59 | Ok(Pubkey::new_from_array(*array_ref![data, 235, 32])) 60 | } 61 | pub fn admin_fee_key_a(self) -> ProtocolResult { 62 | let data = self 63 | .inner() 64 | .try_borrow_data() 65 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 66 | Ok(Pubkey::new_from_array(*array_ref![data, 267, 32])) 67 | } 68 | 69 | pub fn admin_fee_key_b(self) -> ProtocolResult { 70 | let data = self 71 | .inner() 72 | .try_borrow_data() 73 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 74 | Ok(Pubkey::new_from_array(*array_ref![data, 299, 32])) 75 | } 76 | 77 | pub fn nonce(self) -> ProtocolResult { 78 | let data = self 79 | .inner() 80 | .try_borrow_data() 81 | .map_err(|_| ProtocolError::BorrowAccountDataError)?; 82 | Ok(data[2]) 83 | } 84 | } 85 | 86 | #[derive(Copy, Clone)] 87 | pub struct StableSwapArgs<'a, 'b: 'a> { 88 | pub swap_info: StableSwapInfo<'a, 'b>, 89 | pub authority_acc: &'a AccountInfo<'b>, 90 | pub token_a: TokenAccount<'a, 'b>, 91 | pub token_b: TokenAccount<'a, 'b>, 92 | pub admin_fee_acc: &'a AccountInfo<'b>, 93 | pub program_acc: &'a AccountInfo<'b>, 94 | } 95 | 96 | impl<'a, 'b: 'a> StableSwapArgs<'a, 'b> { 97 | const MIN_ACCOUNTS: usize = 6; 98 | 99 | pub fn with_parsed_args(accounts: &'a [AccountInfo<'b>]) -> ProtocolResult { 100 | if accounts.len() < Self::MIN_ACCOUNTS { 101 | return Err(ProtocolError::InvalidAccountsLength); 102 | } 103 | #[allow(clippy::ptr_offset_with_cast)] 104 | let (fixed_accounts, other_accounts) = array_refs![accounts, 5; ..;]; 105 | 106 | let &[ 107 | ref swap_info_acc, 108 | ref authority_acc, 109 | ref token_a_acc, 110 | ref token_b_acc, 111 | ref admin_fee_acc, 112 | //ref clock_sysvar_acc, 113 | //ref program_acc, 114 | ]: &'a[AccountInfo<'b>; 5] = array_ref![fixed_accounts, 0, 5]; 115 | 116 | let program_acc = if other_accounts.len() == 1 { 117 | other_accounts.get(0).unwrap() 118 | } else { 119 | other_accounts.get(1).unwrap() 120 | }; 121 | 122 | let swap_info = StableSwapInfo::new(swap_info_acc)?; 123 | 124 | if swap_info.token_a()? != *token_a_acc.key { 125 | return Err(ProtocolError::InvalidTokenAccount); 126 | } 127 | if swap_info.token_b()? != *token_b_acc.key { 128 | return Err(ProtocolError::InvalidTokenAccount); 129 | } 130 | if !(swap_info.admin_fee_key_a()? == *admin_fee_acc.key 131 | || swap_info.admin_fee_key_b()? == *admin_fee_acc.key) 132 | { 133 | return Err(ProtocolError::InvalidStableSwapAccount); 134 | } 135 | 136 | // validate_authority_pubkey( 137 | // authority_acc.key, 138 | // program_acc.key, 139 | // &swap_info_acc.key.to_bytes(), 140 | // swap_info.nonce()?, 141 | // )?; 142 | 143 | Ok(StableSwapArgs { 144 | swap_info, 145 | authority_acc, 146 | token_a: TokenAccount::new(token_a_acc)?, 147 | token_b: TokenAccount::new(token_b_acc)?, 148 | admin_fee_acc, 149 | program_acc, 150 | }) 151 | } 152 | 153 | pub fn find_token_pair( 154 | &self, 155 | source_token_account_mint: &Pubkey, 156 | ) -> ProtocolResult<(&TokenAccount<'a, 'b>, &TokenAccount<'a, 'b>)> { 157 | if *source_token_account_mint == self.token_a.mint()? { 158 | Ok((&self.token_a, &self.token_b)) 159 | } else { 160 | Ok((&self.token_b, &self.token_a)) 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/program-rust/src/spl_token/error.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/program-rust/src/spl_token/instruction.rs: -------------------------------------------------------------------------------- 1 | //! Instruction types 2 | 3 | use solana_program::{ 4 | instruction::{AccountMeta, Instruction}, 5 | program_error::ProgramError, 6 | pubkey::Pubkey, 7 | }; 8 | use std::mem::size_of; 9 | 10 | use super::check_program_account; 11 | 12 | /// Instructions supported by the token program. 13 | #[repr(C)] 14 | #[derive(Clone, Debug, PartialEq)] 15 | pub enum TokenInstruction { 16 | /// Transfers tokens from one account to another either directly or via a 17 | /// delegate. If this account is associated with the native mint then equal 18 | /// amounts of SOL and Tokens will be transferred to the destination 19 | /// account. 20 | /// 21 | /// Accounts expected by this instruction: 22 | /// 23 | /// * Single owner/delegate 24 | /// 0. `[writable]` The source account. 25 | /// 1. `[writable]` The destination account. 26 | /// 2. `[signer]` The source account's owner/delegate. 27 | /// 28 | /// * Multisignature owner/delegate 29 | /// 0. `[writable]` The source account. 30 | /// 1. `[writable]` The destination account. 31 | /// 2. `[]` The source account's multisignature owner/delegate. 32 | /// 3. ..3+M `[signer]` M signer accounts. 33 | Transfer { 34 | /// The amount of tokens to transfer. 35 | amount: u64, 36 | }, 37 | 38 | /// Close an account by transferring all its SOL to the destination account. 39 | /// Non-native accounts may only be closed if its token amount is zero. 40 | /// 41 | /// Accounts expected by this instruction: 42 | /// 43 | /// * Single owner 44 | /// 0. `[writable]` The account to close. 45 | /// 1. `[writable]` The destination account. 46 | /// 2. `[signer]` The account's owner. 47 | /// 48 | /// * Multisignature owner 49 | /// 0. `[writable]` The account to close. 50 | /// 1. `[writable]` The destination account. 51 | /// 2. `[]` The account's multisignature owner. 52 | /// 3. ..3+M `[signer]` M signer accounts. 53 | CloseAccount, 54 | } 55 | 56 | impl TokenInstruction { 57 | /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer. 58 | pub fn pack(&self) -> Vec { 59 | let mut buf = Vec::with_capacity(size_of::()); 60 | match self { 61 | &Self::Transfer { amount } => { 62 | buf.push(3); 63 | buf.extend_from_slice(&amount.to_le_bytes()); 64 | } 65 | Self::CloseAccount => buf.push(9), 66 | }; 67 | buf 68 | } 69 | } 70 | 71 | /// Creates a `Transfer` instruction. 72 | pub fn transfer( 73 | token_program_id: &Pubkey, 74 | source_pubkey: &Pubkey, 75 | destination_pubkey: &Pubkey, 76 | authority_pubkey: &Pubkey, 77 | signer_pubkeys: &[&Pubkey], 78 | amount: u64, 79 | ) -> Result { 80 | check_program_account(token_program_id)?; 81 | let data = TokenInstruction::Transfer { amount }.pack(); 82 | 83 | let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); 84 | accounts.push(AccountMeta::new(*source_pubkey, false)); 85 | accounts.push(AccountMeta::new(*destination_pubkey, false)); 86 | accounts.push(AccountMeta::new_readonly( 87 | *authority_pubkey, 88 | signer_pubkeys.is_empty(), 89 | )); 90 | for signer_pubkey in signer_pubkeys.iter() { 91 | accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); 92 | } 93 | 94 | Ok(Instruction { 95 | program_id: *token_program_id, 96 | accounts, 97 | data, 98 | }) 99 | } 100 | 101 | #[allow(dead_code)] 102 | /// Creates a `CloseAccount` instruction. 103 | pub fn close_account( 104 | token_program_id: &Pubkey, 105 | account_pubkey: &Pubkey, 106 | destination_pubkey: &Pubkey, 107 | owner_pubkey: &Pubkey, 108 | signer_pubkeys: &[&Pubkey], 109 | ) -> Result { 110 | check_program_account(token_program_id)?; 111 | let data = TokenInstruction::CloseAccount.pack(); 112 | 113 | let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); 114 | accounts.push(AccountMeta::new(*account_pubkey, false)); 115 | accounts.push(AccountMeta::new(*destination_pubkey, false)); 116 | accounts.push(AccountMeta::new_readonly( 117 | *owner_pubkey, 118 | signer_pubkeys.is_empty(), 119 | )); 120 | for signer_pubkey in signer_pubkeys.iter() { 121 | accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); 122 | } 123 | 124 | Ok(Instruction { 125 | program_id: *token_program_id, 126 | accounts, 127 | data, 128 | }) 129 | } 130 | -------------------------------------------------------------------------------- /src/program-rust/src/spl_token/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod instruction; 3 | 4 | use solana_program::{entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey}; 5 | use std::str::FromStr; 6 | 7 | solana_program::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); 8 | 9 | /// Checks that the supplied program ID is the correct one for SPL-token 10 | pub fn check_program_account(spl_token_program_id: &Pubkey) -> ProgramResult { 11 | if spl_token_program_id != &id() { 12 | return Err(ProgramError::IncorrectProgramId); 13 | } 14 | Ok(()) 15 | } 16 | 17 | lazy_static::lazy_static! { 18 | pub static ref PROGRAM_ID: Pubkey = Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap(); 19 | } 20 | 21 | pub const ACCOUNT_LEN: usize = 165; 22 | pub const MINT_LEN: usize = 82; 23 | -------------------------------------------------------------------------------- /src/program-rust/src/state.rs: -------------------------------------------------------------------------------- 1 | //! State transition types 2 | use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; 3 | use solana_program::{ 4 | program_error::ProgramError, 5 | program_option::COption, 6 | program_pack::{IsInitialized, Pack, Sealed}, 7 | pubkey::Pubkey, 8 | }; 9 | 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 11 | pub enum Status { 12 | SwapInfo, 13 | Closed, 14 | } 15 | 16 | impl Status { 17 | pub fn from_u8(status: u8) -> Result { 18 | match status { 19 | 1 => Ok(Status::SwapInfo), 20 | 3 => Ok(Status::Closed), 21 | _ => Err(ProgramError::InvalidArgument), 22 | } 23 | } 24 | 25 | pub fn to_u8(&self) -> u8 { 26 | match self { 27 | Status::SwapInfo => 1, 28 | Status::Closed => 3, 29 | } 30 | } 31 | } 32 | 33 | #[repr(C)] 34 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 35 | pub struct SwapInfo { 36 | /// Initialized state. 37 | pub is_initialized: u8, 38 | /// nonce used in program address. 39 | pub status: u8, 40 | /// latest amount 41 | pub token_latest_amount: u64, 42 | /// Owner address 43 | pub owner: Pubkey, 44 | /// token account 45 | pub token_account: COption, 46 | } 47 | 48 | impl SwapInfo { 49 | pub fn new(owner: &Pubkey) -> Self { 50 | Self { 51 | is_initialized: 1, 52 | status: Status::SwapInfo.to_u8(), 53 | token_latest_amount: 0, 54 | owner: *owner, 55 | token_account: COption::None, 56 | } 57 | } 58 | } 59 | 60 | impl Sealed for SwapInfo {} 61 | 62 | impl IsInitialized for SwapInfo { 63 | fn is_initialized(&self) -> bool { 64 | self.is_initialized == 1 65 | } 66 | } 67 | 68 | impl Pack for SwapInfo { 69 | const LEN: usize = 78; 70 | 71 | fn pack_into_slice(&self, dst: &mut [u8]) { 72 | let output = array_mut_ref![dst, 0, 78]; 73 | #[rustfmt::skip] 74 | let ( 75 | is_initialized, 76 | status, 77 | token_latest_amount, 78 | owner, 79 | token_account, 80 | ) = mut_array_refs![output, 1, 1, 8, 32, 36]; 81 | is_initialized.copy_from_slice(&[self.is_initialized]); 82 | status.copy_from_slice(&[self.status]); 83 | token_latest_amount.copy_from_slice(&self.token_latest_amount.to_le_bytes()[..]); 84 | owner.copy_from_slice(self.owner.as_ref()); 85 | pack_coption_key(&self.token_account, token_account); 86 | } 87 | 88 | fn unpack_from_slice(src: &[u8]) -> Result { 89 | let input = array_ref![src, 0, 78]; 90 | #[rustfmt::skip] 91 | let ( 92 | &[is_initialized], 93 | &[status], 94 | &token_latest_amount, 95 | owner, 96 | token_account, 97 | ) = array_refs![input, 1, 1, 8, 32, 36]; 98 | Ok(Self { 99 | is_initialized, 100 | status, 101 | token_latest_amount: u64::from_le_bytes(token_latest_amount), 102 | owner: Pubkey::new(owner), 103 | token_account: unpack_coption_key(token_account)?, 104 | }) 105 | } 106 | } 107 | 108 | fn pack_coption_key(src: &COption, dst: &mut [u8; 36]) { 109 | let (tag, body) = mut_array_refs![dst, 4, 32]; 110 | match src { 111 | COption::Some(key) => { 112 | *tag = [1, 0, 0, 0]; 113 | body.copy_from_slice(key.as_ref()); 114 | } 115 | COption::None => { 116 | *tag = [0; 4]; 117 | } 118 | } 119 | } 120 | 121 | fn unpack_coption_key(src: &[u8; 36]) -> Result, ProgramError> { 122 | let (tag, body) = array_refs![src, 4, 32]; 123 | match *tag { 124 | [0, 0, 0, 0] => Ok(COption::None), 125 | [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))), 126 | _ => Err(ProgramError::InvalidAccountData), 127 | } 128 | } 129 | 130 | #[cfg(test)] 131 | mod test { 132 | // use super::*; 133 | 134 | // const TEST_VERSION: u8 = 1; 135 | // const TEST_NONCE: u8 = 255; 136 | // const TEST_TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]); 137 | // const TEST_TOKEN: Pubkey = Pubkey::new_from_array([2u8; 32]); 138 | // const TEST_TOKEN_MINT: Pubkey = Pubkey::new_from_array([5u8; 32]); 139 | 140 | #[test] 141 | pub fn test_onesol_amm_info() { 142 | assert_eq!(1, 1); 143 | } 144 | } 145 | --------------------------------------------------------------------------------