wip/gobbler-lps

This commit is contained in:
stacc 2024-10-11 18:12:04 +01:00
parent 7f1b42f70f
commit 60acc16480
5 changed files with 553 additions and 344 deletions

7
Cargo.lock generated
View File

@ -2111,6 +2111,9 @@ dependencies = [
"solana-program-test", "solana-program-test",
"solana-sdk", "solana-sdk",
"spl-associated-token-account 1.1.3", "spl-associated-token-account 1.1.3",
"spl-memo 3.0.1",
"spl-token 3.5.0",
"spl-token-2022 0.9.0",
"tracing", "tracing",
] ]
@ -5358,7 +5361,7 @@ dependencies = [
[[package]] [[package]]
name = "quic-geyser-client" name = "quic-geyser-client"
version = "0.1.5" version = "0.1.5"
source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_new_streaming#b0fa0a2f50240ab6c1e7a59b928ea74da9a6785b" source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_v1.17.29#594077ccebfde826920192c17b0e501bb185650e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -5375,7 +5378,7 @@ dependencies = [
[[package]] [[package]]
name = "quic-geyser-common" name = "quic-geyser-common"
version = "0.1.5" version = "0.1.5"
source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_new_streaming#b0fa0a2f50240ab6c1e7a59b928ea74da9a6785b" source = "git+https://github.com/blockworks-foundation/quic_geyser_plugin.git?branch=router_v1.17.29#594077ccebfde826920192c17b0e501bb185650e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",

View File

@ -30,6 +30,10 @@ serde_derive = "1.0"
mango-feeds-connector = { workspace = true } mango-feeds-connector = { workspace = true }
# raydium-cp # raydium-cp
raydium-cp-swap = "0.1.143" raydium-cp-swap = "0.1.143"
spl-memo = "3.0.0"
spl-token = "3.5.0"
spl-token-2022 = "0.9.0"
[dev-dependencies] [dev-dependencies]
router-test-lib = { path = "../router-test-lib", version = "0.1" } router-test-lib = { path = "../router-test-lib", version = "0.1" }

View File

@ -7,7 +7,9 @@ use anchor_spl::token_2022::spl_token_2022::extension::{
}; };
use anchor_spl::token_2022::spl_token_2022::state::Mint; use anchor_spl::token_2022::spl_token_2022::state::Mint;
use mango_feeds_connector::chain_data::AccountData; use mango_feeds_connector::chain_data::AccountData;
use raydium_cp_swap::curve::{ConstantProductCurve, CurveCalculator, Fees, TradeDirection}; use raydium_cp_swap::curve::{
ConstantProductCurve, CurveCalculator, Fees, TradeDirection,
};
use raydium_cp_swap::states::{AmmConfig, PoolState, PoolStatusBitIndex}; use raydium_cp_swap::states::{AmmConfig, PoolState, PoolStatusBitIndex};
use solana_program::clock::Clock; use solana_program::clock::Clock;
use solana_program::pubkey::Pubkey; use solana_program::pubkey::Pubkey;
@ -18,11 +20,30 @@ use std::panic;
use router_lib::dex::{DexEdge, DexEdgeIdentifier}; use router_lib::dex::{DexEdge, DexEdgeIdentifier};
/// Enums to represent operations and directions
#[derive(Clone, Copy, Debug)]
pub enum Operation {
Swap,
Deposit,
Withdraw,
}
#[derive(Clone, Copy, Debug)]
pub enum Direction {
AtoB,
BtoA,
AandBtoLP,
LPtoAandB,
}
/// Modify GobblerEdgeIdentifier to include operation and direction
pub struct GobblerEdgeIdentifier { pub struct GobblerEdgeIdentifier {
pub pool: Pubkey, pub pool: Pubkey,
pub mint_a: Pubkey, pub mint_a: Pubkey,
pub mint_b: Pubkey, pub mint_b: Pubkey,
pub is_a_to_b: bool, pub lp_mint: Pubkey,
pub operation: Operation,
pub direction: Direction,
} }
impl DexEdgeIdentifier for GobblerEdgeIdentifier { impl DexEdgeIdentifier for GobblerEdgeIdentifier {
@ -31,19 +52,41 @@ impl DexEdgeIdentifier for GobblerEdgeIdentifier {
} }
fn desc(&self) -> String { fn desc(&self) -> String {
format!("Gobbler_{}", self.pool) format!(
"Gobbler_{}_{}_{}",
self.operation_string(),
self.direction_string(),
self.pool
)
} }
fn input_mint(&self) -> Pubkey { fn input_mint(&self) -> Pubkey {
self.mint_a match self.operation {
Operation::Swap => match self.direction {
Direction::AtoB => self.mint_a,
Direction::BtoA => self.mint_b,
_ => self.mint_a, // Default case
},
Operation::Deposit => self.mint_a, // For deposit, input mints are mint_a and mint_b
Operation::Withdraw => self.lp_mint,
}
} }
fn output_mint(&self) -> Pubkey { fn output_mint(&self) -> Pubkey {
self.mint_b match self.operation {
Operation::Swap => match self.direction {
Direction::AtoB => self.mint_b,
Direction::BtoA => self.mint_a,
_ => self.mint_b, // Default case
},
Operation::Deposit => self.lp_mint,
Operation::Withdraw => self.mint_a, // For withdraw, output mints are mint_a and mint_b
}
} }
fn accounts_needed(&self) -> usize { fn accounts_needed(&self) -> usize {
11 // Adjust based on operation
11 // This might vary depending on actual requirements
} }
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
@ -51,6 +94,26 @@ impl DexEdgeIdentifier for GobblerEdgeIdentifier {
} }
} }
impl GobblerEdgeIdentifier {
fn operation_string(&self) -> &'static str {
match self.operation {
Operation::Swap => "Swap",
Operation::Deposit => "Deposit",
Operation::Withdraw => "Withdraw",
}
}
fn direction_string(&self) -> &'static str {
match self.direction {
Direction::AtoB => "AtoB",
Direction::BtoA => "BtoA",
Direction::AandBtoLP => "AandBtoLP",
Direction::LPtoAandB => "LPtoAandB",
}
}
}
/// Modify GobblerEdge to include operation and direction
pub struct GobblerEdge { pub struct GobblerEdge {
pub pool: PoolState, pub pool: PoolState,
pub config: AmmConfig, pub config: AmmConfig,
@ -58,6 +121,9 @@ pub struct GobblerEdge {
pub vault_1_amount: u64, pub vault_1_amount: u64,
pub mint_0: Option<TransferFeeConfig>, pub mint_0: Option<TransferFeeConfig>,
pub mint_1: Option<TransferFeeConfig>, pub mint_1: Option<TransferFeeConfig>,
pub lp_supply: u64,
pub operation: Operation,
pub direction: Direction,
} }
impl DexEdge for GobblerEdge { impl DexEdge for GobblerEdge {
@ -94,11 +160,8 @@ pub fn swap_base_input(
amount_in: u64, amount_in: u64,
) -> anyhow::Result<(u64, u64, u64)> { ) -> anyhow::Result<(u64, u64, u64)> {
let res = panic::catch_unwind(|| { let res = panic::catch_unwind(|| {
let pool_state = pool; let block_timestamp = Clock::get()?.unix_timestamp as u64;
let block_timestamp = pool_state.open_time + 1; // TODO: This should be the actual clock if !pool.get_status_by_bit(PoolStatusBitIndex::Swap) || block_timestamp < pool.open_time {
if !pool_state.get_status_by_bit(PoolStatusBitIndex::Swap)
|| block_timestamp < pool_state.open_time
{
return Err(anyhow!("Pool is not trading")); return Err(anyhow!("Pool is not trading"));
} }
@ -108,48 +171,53 @@ pub fn swap_base_input(
return Err(anyhow!("Amount too low after transfer fee")); return Err(anyhow!("Amount too low after transfer fee"));
} }
let (total_input_token_amount, total_output_token_amount) = if input_vault_key == pool_state.token_0_vault let (total_input_token_amount, total_output_token_amount) =
&& output_vault_key == pool_state.token_1_vault if input_vault_key == pool.token_0_vault && output_vault_key == pool.token_1_vault {
{ vault_amount_without_fee(pool, input_vault_amount, output_vault_amount)?
vault_amount_without_fee(pool_state, input_vault_amount, output_vault_amount)? } else if input_vault_key == pool.token_1_vault && output_vault_key == pool.token_0_vault
} else if input_vault_key == pool_state.token_1_vault {
&& output_vault_key == pool_state.token_0_vault let (out, inp) =
{ vault_amount_without_fee(pool, output_vault_amount, input_vault_amount)?;
let (out, inp) = vault_amount_without_fee(pool_state, output_vault_amount, input_vault_amount)?; (inp, out)
(inp, out) } else {
} else { return Err(anyhow!("Invalid vault configuration"));
return Err(anyhow!("Invalid vault configuration")); };
};
let (input_token_creator_rate, input_token_lp_rate) =
if input_vault_key == pool.token_0_vault {
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
} else {
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let (input_token_creator_rate, input_token_lp_rate) = if input_vault_key == pool_state.token_0_vault { let protocol_fee = (amm_config.token_0_creator_rate
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
} else {
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let protocol_fee = (amm_config.token_0_creator_rate
+ amm_config.token_1_creator_rate + amm_config.token_1_creator_rate
+ amm_config.token_0_lp_rate + amm_config.token_0_lp_rate
+ amm_config.token_1_lp_rate) / 10000; + amm_config.token_1_lp_rate)
/ 10000;
let swap_result = raydium_cp_swap::curve::CurveCalculator::swap_base_input( let swap_result = CurveCalculator::swap_base_input(
actual_amount_in.into(), actual_amount_in.into(),
total_input_token_amount.into(), total_input_token_amount.into(),
total_output_token_amount.into(), total_output_token_amount.into(),
protocol_fee+input_token_creator_rate+input_token_lp_rate, protocol_fee + input_token_creator_rate + input_token_lp_rate,
input_token_creator_rate, input_token_creator_rate,
input_token_lp_rate, input_token_lp_rate,
).ok_or(anyhow!("Swap calculation failed"))?; )
.ok_or(anyhow!("Swap calculation failed"))?;
let output_transfer_fee = get_transfer_fee(output_mint, swap_result.destination_amount_swapped.try_into()?)?; let output_transfer_fee =
let amount_received = swap_result.destination_amount_swapped.saturating_sub(output_transfer_fee.into()); get_transfer_fee(output_mint, swap_result.destination_amount_swapped.try_into()?)?;
let amount_received = swap_result
.destination_amount_swapped
.saturating_sub(output_transfer_fee.into());
Ok(( Ok((
amount_in, amount_in,
amount_received.try_into().map_err(|e| anyhow!("Failed to convert amount_received: {}", e))?, amount_received.try_into()?,
(protocol_fee+input_token_creator_rate+input_token_lp_rate).try_into().map_err(|e| anyhow!("Failed to convert fees: {}", e))?, (protocol_fee + input_token_creator_rate + input_token_lp_rate)
.try_into()
.map_err(|e| anyhow!("Failed to convert fees: {}", e))?,
)) ))
}); });
@ -162,18 +230,15 @@ pub fn swap_base_output(
amm_config: &AmmConfig, amm_config: &AmmConfig,
input_vault_key: Pubkey, input_vault_key: Pubkey,
input_vault_amount: u64, input_vault_amount: u64,
_input_mint: &Option<TransferFeeConfig>, input_mint: &Option<TransferFeeConfig>,
output_vault_key: Pubkey, output_vault_key: Pubkey,
output_vault_amount: u64, output_vault_amount: u64,
output_mint: &Option<TransferFeeConfig>, output_mint: &Option<TransferFeeConfig>,
amount_out: u64, amount_out: u64,
) -> anyhow::Result<(u64, u64, u64)> { ) -> anyhow::Result<(u64, u64, u64)> {
let res = panic::catch_unwind(|| { let res = panic::catch_unwind(|| {
let pool_state = pool; let block_timestamp = Clock::get()?.unix_timestamp as u64;
let block_timestamp = pool_state.open_time + 1; // TODO: This should be the actual clock if !pool.get_status_by_bit(PoolStatusBitIndex::Swap) || block_timestamp < pool.open_time {
if !pool_state.get_status_by_bit(PoolStatusBitIndex::Swap)
|| block_timestamp < pool_state.open_time
{
return Err(anyhow!("Pool is not trading")); return Err(anyhow!("Pool is not trading"));
} }
@ -182,53 +247,62 @@ pub fn swap_base_output(
} }
let output_transfer_fee = get_transfer_fee(output_mint, amount_out)?; let output_transfer_fee = get_transfer_fee(output_mint, amount_out)?;
let actual_amount_out = amount_out.checked_add(output_transfer_fee) let actual_amount_out = amount_out
.checked_add(output_transfer_fee)
.ok_or_else(|| anyhow!("Output amount overflow"))?; .ok_or_else(|| anyhow!("Output amount overflow"))?;
let (total_input_token_amount, total_output_token_amount) = if input_vault_key == pool_state.token_0_vault let (total_input_token_amount, total_output_token_amount) =
&& output_vault_key == pool_state.token_1_vault if input_vault_key == pool.token_0_vault && output_vault_key == pool.token_1_vault {
{ vault_amount_without_fee(pool, input_vault_amount, output_vault_amount)?
vault_amount_without_fee(pool_state, input_vault_amount, output_vault_amount)? } else if input_vault_key == pool.token_1_vault && output_vault_key == pool.token_0_vault
} else if input_vault_key == pool_state.token_1_vault {
&& output_vault_key == pool_state.token_0_vault let (out, inp) =
{ vault_amount_without_fee(pool, output_vault_amount, input_vault_amount)?;
let (out, inp) = vault_amount_without_fee(pool_state, output_vault_amount, input_vault_amount)?; (inp, out)
(inp, out) } else {
} else { return Err(anyhow!("Invalid vault configuration"));
return Err(anyhow!("Invalid vault configuration")); };
};
if total_output_token_amount < actual_amount_out.into() { if total_output_token_amount < actual_amount_out.into() {
return Err(anyhow!("Insufficient liquidity")); return Err(anyhow!("Insufficient liquidity"));
} }
let (input_token_creator_rate, input_token_lp_rate) = if input_vault_key == pool_state.token_0_vault { let (input_token_creator_rate, input_token_lp_rate) =
(amm_config.token_0_creator_rate, amm_config.token_0_lp_rate) if input_vault_key == pool.token_0_vault {
} else { (amm_config.token_0_creator_rate, amm_config.token_0_lp_rate)
(amm_config.token_1_creator_rate, amm_config.token_1_lp_rate) } else {
}; (amm_config.token_1_creator_rate, amm_config.token_1_lp_rate)
};
let protocol_fee = (amm_config.token_0_creator_rate let protocol_fee = (amm_config.token_0_creator_rate
+ amm_config.token_1_creator_rate + amm_config.token_1_creator_rate
+ amm_config.token_0_lp_rate + amm_config.token_0_lp_rate
+ amm_config.token_1_lp_rate) / 10000; + amm_config.token_1_lp_rate)
/ 10000;
let swap_result = raydium_cp_swap::curve::CurveCalculator::swap_base_output( let swap_result = CurveCalculator::swap_base_output(
actual_amount_out.into(), actual_amount_out.into(),
total_input_token_amount.into(), total_input_token_amount.into(),
total_output_token_amount.into(), total_output_token_amount.into(),
protocol_fee+input_token_creator_rate+input_token_lp_rate, protocol_fee + input_token_creator_rate + input_token_lp_rate,
input_token_creator_rate, input_token_creator_rate,
input_token_lp_rate, input_token_lp_rate,
).ok_or(anyhow!("Swap calculation failed"))?; )
.ok_or(anyhow!("Swap calculation failed"))?;
let input_transfer_fee = get_transfer_fee(_input_mint, swap_result.source_amount_swapped.try_into()?)?; let input_transfer_fee =
let amount_in = swap_result.source_amount_swapped.saturating_add(input_transfer_fee.into()); get_transfer_fee(input_mint, swap_result.source_amount_swapped.try_into()?)?;
let amount_in = swap_result
.source_amount_swapped
.saturating_add(input_transfer_fee.into());
Ok(( Ok((
amount_in.try_into().map_err(|e| anyhow!("Failed to convert amount_in: {}", e))?, amount_in.try_into()?,
amount_out, amount_out,
swap_result.total_fees.try_into().map_err(|e| anyhow!("Failed to convert fees: {}", e))?, swap_result
.total_fees
.try_into()
.map_err(|e| anyhow!("Failed to convert fees: {}", e))?,
)) ))
}); });
@ -242,7 +316,7 @@ pub fn get_transfer_fee(
let fee = if let Some(transfer_fee_config) = mint_info { let fee = if let Some(transfer_fee_config) = mint_info {
transfer_fee_config transfer_fee_config
.calculate_epoch_fee(Clock::get()?.epoch, pre_fee_amount) .calculate_epoch_fee(Clock::get()?.epoch, pre_fee_amount)
.unwrap() .unwrap_or(0)
} else { } else {
0 0
}; };
@ -257,9 +331,9 @@ pub fn vault_amount_without_fee(
Ok(( Ok((
vault_0 vault_0
.checked_sub(pool.protocol_fees_token_0 + pool.fund_fees_token_0) .checked_sub(pool.protocol_fees_token_0 + pool.fund_fees_token_0)
.ok_or(anyhow::format_err!("invalid sub"))?, .ok_or(anyhow::format_err!("Invalid subtraction for vault_0"))?,
vault_1 vault_1
.checked_sub(pool.protocol_fees_token_1 + pool.fund_fees_token_1) .checked_sub(pool.protocol_fees_token_1 + pool.fund_fees_token_1)
.ok_or(anyhow::format_err!("invalid sub"))?, .ok_or(anyhow::format_err!("Invalid subtraction for vault_1"))?,
)) ))
} }

View File

@ -1,9 +1,8 @@
use crate::edge::{swap_base_input, swap_base_output, GobblerEdge, GobblerEdgeIdentifier}; use crate::edge::{ swap_base_input, swap_base_output, Direction, GobblerEdge, GobblerEdgeIdentifier, Operation, _get_transfer_config};
use crate::gobbler_ix_builder; use crate::gobbler_ix_builder;
use anchor_lang::{AccountDeserialize, Discriminator, Id}; use anchor_lang::{AccountDeserialize, Discriminator, Id};
use anchor_spl::token::spl_token::state::AccountState; use anchor_spl::token::spl_token::state::AccountState;
use anchor_spl::token::{spl_token, Token}; use anchor_spl::token::{spl_token, Token};
use anchor_spl::token_2022::spl_token_2022;
use anyhow::Context; use anyhow::Context;
use async_trait::async_trait; use async_trait::async_trait;
use itertools::Itertools; use itertools::Itertools;
@ -43,82 +42,108 @@ impl DexInterface for GobblerDex {
where where
Self: Sized, Self: Sized,
{ {
let pools = // Fetch all PoolState accounts
fetch_raydium_account::<PoolState>(rpc, raydium_cp_swap::id(), PoolState::LEN).await?; let pools = fetch_gobbler_pools(rpc).await?;
// Collect vaults to identify any banned ones (e.g., frozen accounts)
let vaults = pools let vaults = pools
.iter() .iter()
.flat_map(|x| [x.1.token_0_vault, x.1.token_1_vault]) .flat_map(|(_, pool)| vec![pool.token_0_vault, pool.token_1_vault])
.collect::<HashSet<_>>();
let vaults = rpc.get_multiple_accounts(&vaults).await?;
let banned_vaults = vaults
.iter()
.filter(|x| {
x.1.owner == Token::id()
&& spl_token::state::Account::unpack(x.1.data()).unwrap().state
== AccountState::Frozen
})
.map(|x| x.0)
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let pools = pools let vault_accounts = rpc.get_multiple_accounts(&vaults).await?;
let banned_vaults = vault_accounts
.iter() .iter()
.filter(|(_pool_pk, pool)| { .filter(|(_, account)| {
pool.token_0_program == Token::id() && pool.token_1_program == Token::id() account.owner == Token::id()
// TODO Remove filter when 2022 are working && spl_token::state::Account::unpack(account.data())
.map(|acc| acc.state == AccountState::Frozen)
.unwrap_or(false)
}) })
.filter(|(_pool_pk, pool)| { .map(|(pubkey, _)| *pubkey)
!banned_vaults.contains(&pool.token_0_vault) .collect::<HashSet<_>>();
// Filter out pools with banned vaults or unsupported token programs
let valid_pools = pools
.into_iter()
.filter(|(_, pool)| {
pool.token_0_program == Token::id()
&& pool.token_1_program == Token::id()
&& !banned_vaults.contains(&pool.token_0_vault)
&& !banned_vaults.contains(&pool.token_1_vault) && !banned_vaults.contains(&pool.token_1_vault)
}) })
.collect_vec(); .collect::<Vec<_>>();
let edge_pairs = pools // Create edge identifiers for each pool
.iter() let mut edge_identifiers = Vec::new();
.map(|(pool_pk, pool)| {
( for (pool_pk, pool) in &valid_pools {
Arc::new(GobblerEdgeIdentifier { // Swap edges between Token A and Token B
pool: *pool_pk, let swap_a_to_b = Arc::new(GobblerEdgeIdentifier {
mint_a: pool.token_0_mint, pool: *pool_pk,
mint_b: pool.token_1_mint, mint_a: pool.token_0_mint,
is_a_to_b: true, mint_b: pool.token_1_mint,
}), lp_mint: pool.lp_mint,
Arc::new(GobblerEdgeIdentifier { operation: Operation::Swap,
pool: *pool_pk, direction: Direction::AtoB,
mint_a: pool.token_1_mint, });
mint_b: pool.token_0_mint,
is_a_to_b: false, let swap_b_to_a = Arc::new(GobblerEdgeIdentifier {
}), pool: *pool_pk,
) mint_a: pool.token_1_mint,
}) mint_b: pool.token_0_mint,
.collect_vec(); lp_mint: pool.lp_mint,
operation: Operation::Swap,
direction: Direction::BtoA,
});
// Deposit edge from (Token A, Token B) to LP Token
let deposit_edge = Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_0_mint,
mint_b: pool.token_1_mint,
lp_mint: pool.lp_mint,
operation: Operation::Deposit,
direction: Direction::AandBtoLP,
});
// Withdrawal edge from LP Token to (Token A, Token B)
let withdraw_edge = Arc::new(GobblerEdgeIdentifier {
pool: *pool_pk,
mint_a: pool.token_0_mint,
mint_b: pool.token_1_mint,
lp_mint: pool.lp_mint,
operation: Operation::Withdraw,
direction: Direction::LPtoAandB,
});
edge_identifiers.push(swap_a_to_b);
edge_identifiers.push(swap_b_to_a);
edge_identifiers.push(deposit_edge);
edge_identifiers.push(withdraw_edge);
}
let mut needed_accounts = HashSet::new(); let mut needed_accounts = HashSet::new();
let mut edges_per_pk = HashMap::new();
let edges_per_pk = { for (pool_pk, pool) in &valid_pools {
let mut map = HashMap::new(); // Collect all necessary accounts
for ((pool_pk, pool), (edge_a_to_b, edge_b_to_a)) in pools.iter().zip(edge_pairs.iter()) needed_accounts.insert(*pool_pk);
{ needed_accounts.insert(pool.amm_config);
let entry = vec![ needed_accounts.insert(pool.token_0_vault);
edge_a_to_b.clone() as Arc<dyn DexEdgeIdentifier>, needed_accounts.insert(pool.token_1_vault);
edge_b_to_a.clone(), needed_accounts.insert(pool.lp_mint);
]; needed_accounts.insert(pool.token_0_mint);
needed_accounts.insert(pool.token_1_mint);
utils::insert_or_extend(&mut map, pool_pk, &entry); // Map edges to their corresponding public keys
utils::insert_or_extend(&mut map, &pool.amm_config, &entry); let edges = edge_identifiers
utils::insert_or_extend(&mut map, &pool.token_0_vault, &entry); .iter()
utils::insert_or_extend(&mut map, &pool.token_1_vault, &entry); .filter(|edge| edge.as_any().downcast_ref::<GobblerEdgeIdentifier>().unwrap().pool == *pool_pk)
.cloned()
needed_accounts.insert(*pool_pk); .collect::<Vec<_>>();
needed_accounts.insert(pool.amm_config); utils::insert_or_extend(&mut edges_per_pk, pool_pk, &edges.into_iter().map(|edge| edge as Arc<dyn DexEdgeIdentifier>).collect::<Vec<_>>());
needed_accounts.insert(pool.token_0_vault); }
needed_accounts.insert(pool.token_1_vault);
// TODO Uncomment for Token-2022
// needed_accounts.insert(pool.token_0_mint);
// needed_accounts.insert(pool.token_1_mint);
}
map
};
Ok(Arc::new(GobblerDex { Ok(Arc::new(GobblerDex {
edges: edges_per_pk, edges: edges_per_pk,
@ -132,12 +157,9 @@ impl DexInterface for GobblerDex {
fn subscription_mode(&self) -> DexSubscriptionMode { fn subscription_mode(&self) -> DexSubscriptionMode {
DexSubscriptionMode::Mixed(MixedDexSubscription { DexSubscriptionMode::Mixed(MixedDexSubscription {
accounts: Default::default(), accounts: self.needed_accounts.clone(),
programs: HashSet::from([raydium_cp_swap::id()]), programs: HashSet::from([raydium_cp_swap::id()]),
token_accounts_for_owner: HashSet::from([Pubkey::from_str( token_accounts_for_owner: HashSet::new(),
"GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL",
)
.unwrap()]),
}) })
} }
@ -157,35 +179,39 @@ impl DexInterface for GobblerDex {
let id = id let id = id
.as_any() .as_any()
.downcast_ref::<GobblerEdgeIdentifier>() .downcast_ref::<GobblerEdgeIdentifier>()
.unwrap(); .context("Invalid edge identifier type")?;
let pool_account = chain_data.account(&id.pool)?; let pool_account = chain_data.account(&id.pool)?;
let pool = PoolState::try_deserialize(&mut pool_account.account.data())?; let pool = PoolState::try_deserialize(&mut pool_account.account.data())?;
let config_account = chain_data.account(&pool.amm_config)?; let config_account = chain_data.account(&pool.amm_config)?;
let config = AmmConfig::try_deserialize(&mut config_account.account.data())?; let config = AmmConfig::try_deserialize(&mut config_account.account.data())?;
let vault_0_account = chain_data.account(&pool.token_0_vault)?; let vault_0_account = chain_data.account(&pool.token_0_vault)?;
let vault_0 = spl_token_2022::state::Account::unpack(vault_0_account.account.data())?; let vault_0 = spl_token::state::Account::unpack(&vault_0_account.account.data())?;
let vault_1_account = chain_data.account(&pool.token_1_vault)?; let vault_1_account = chain_data.account(&pool.token_1_vault)?;
let vault_1 = spl_token_2022::state::Account::unpack(vault_1_account.account.data())?; let vault_1 = spl_token::state::Account::unpack(&vault_1_account.account.data())?;
let transfer_0_fee = None; let lp_mint_account = chain_data.account(&pool.lp_mint)?;
let transfer_1_fee = None; let lp_mint = spl_token::state::Mint::unpack(&lp_mint_account.account.data())?;
// TODO Uncomment for Token-2022 let mint_0_account = chain_data.account(&pool.token_0_mint)?;
// let mint_0_account = chain_data.account(&pool.token_0_mint)?; let mint_1_account = chain_data.account(&pool.token_1_mint)?;
// let mint_1_account = chain_data.account(&pool.token_1_mint)?;
// let transfer_0_fee = crate::edge::get_transfer_config(mint_0_account)?; let mint_0 = _get_transfer_config(&mint_0_account)?;
// let transfer_1_fee = crate::edge::get_transfer_config(mint_1_account)?; let mint_1 = _get_transfer_config(&mint_1_account)?;
Ok(Arc::new(GobblerEdge { Ok(Arc::new(GobblerEdge {
pool, pool,
config, config,
vault_0_amount: vault_0.amount, vault_0_amount: vault_0.amount,
vault_1_amount: vault_1.amount, vault_1_amount: vault_1.amount,
mint_0: transfer_0_fee, mint_0,
mint_1: transfer_1_fee, mint_1,
lp_supply: lp_mint.supply,
operation: id.operation,
direction: id.direction,
})) }))
} }
@ -199,69 +225,36 @@ impl DexInterface for GobblerDex {
let id = id let id = id
.as_any() .as_any()
.downcast_ref::<GobblerEdgeIdentifier>() .downcast_ref::<GobblerEdgeIdentifier>()
.unwrap(); .context("Invalid edge identifier type")?;
let edge = edge.as_any().downcast_ref::<GobblerEdge>().unwrap(); let edge = edge.as_any().downcast_ref::<GobblerEdge>().context("Invalid edge type")?;
if !edge.pool.get_status_by_bit(PoolStatusBitIndex::Swap) { match id.operation {
return Ok(Quote { Operation::Swap => {
in_amount: 0, // Handle swap operation
out_amount: 0, // Similar to previous implementation
fee_amount: 0, // ...
fee_mint: edge.pool.token_0_mint, }
}); Operation::Deposit => {
// Handle deposit operation
// Calculate LP tokens received for given in_amounts of Token A and Token B
// Return a Quote with LP tokens as out_amount
// ...
}
Operation::Withdraw => {
// Handle withdrawal operation
// Calculate amounts of Token A and Token B received for given in_amount of LP tokens
// Return a Quote with total value of tokens received as out_amount
// ...
}
} }
let clock = chain_data.account(&Clock::id()).context("read clock")?; // Placeholder return until implementation is provided
let now_ts = clock.account.deserialize_data::<Clock>()?.unix_timestamp as u64; Ok(Quote {
if edge.pool.open_time > now_ts { in_amount: 0,
return Ok(Quote { out_amount: 0,
in_amount: 0, fee_amount: 0,
out_amount: 0, fee_mint: id.mint_a,
fee_amount: 0, })
fee_mint: edge.pool.token_0_mint,
});
}
let quote = if id.is_a_to_b {
let result = swap_base_input(
&edge.pool,
&edge.config,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
in_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_0_mint,
}
} else {
let result = swap_base_input(
&edge.pool,
&edge.config,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
in_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_1_mint,
}
};
Ok(quote)
} }
fn build_swap_ix( fn build_swap_ix(
@ -276,15 +269,38 @@ impl DexInterface for GobblerDex {
let id = id let id = id
.as_any() .as_any()
.downcast_ref::<GobblerEdgeIdentifier>() .downcast_ref::<GobblerEdgeIdentifier>()
.unwrap(); .context("Invalid edge identifier type")?;
gobbler_ix_builder::build_swap_ix(
id, match id.operation {
chain_data, Operation::Swap => {
wallet_pk, gobbler_ix_builder::build_swap_ix(
in_amount, id,
out_amount, chain_data,
max_slippage_bps, wallet_pk,
) in_amount,
out_amount,
max_slippage_bps,
)
}
Operation::Deposit => {
gobbler_ix_builder::build_deposit_ix(
id,
chain_data,
wallet_pk,
in_amount, // Adjust as needed
max_slippage_bps,
)
}
Operation::Withdraw => {
gobbler_ix_builder::build_withdraw_ix(
id,
chain_data,
wallet_pk,
in_amount, // Adjust as needed
max_slippage_bps,
)
}
}
} }
fn supports_exact_out(&self, _id: &Arc<dyn DexEdgeIdentifier>) -> bool { fn supports_exact_out(&self, _id: &Arc<dyn DexEdgeIdentifier>) -> bool {
@ -301,81 +317,46 @@ impl DexInterface for GobblerDex {
let id = id let id = id
.as_any() .as_any()
.downcast_ref::<GobblerEdgeIdentifier>() .downcast_ref::<GobblerEdgeIdentifier>()
.unwrap(); .context("Invalid edge identifier type")?;
let edge = edge.as_any().downcast_ref::<GobblerEdge>().unwrap(); let edge = edge.as_any().downcast_ref::<GobblerEdge>().context("Invalid edge type")?;
if !edge.pool.get_status_by_bit(PoolStatusBitIndex::Swap) { match id.operation {
return Ok(Quote { Operation::Swap => {
in_amount: u64::MAX, // Handle swap operation
out_amount: 0, // Similar to previous implementation
fee_amount: 0, // ...
fee_mint: edge.pool.token_0_mint, }
}); Operation::Deposit => {
// Handle deposit operation
// Calculate amounts of Token A and Token B required to receive given out_amount of LP tokens
// Return a Quote with total value of tokens required as in_amount
// ...
}
Operation::Withdraw => {
// Handle withdrawal operation
// Calculate in_amount of LP tokens required to receive given out_amounts of Token A and Token B
// Return a Quote with LP tokens as in_amount
// ...
}
} }
let clock = chain_data.account(&Clock::id()).context("read clock")?; // Placeholder return until implementation is provided
let now_ts = clock.account.deserialize_data::<Clock>()?.unix_timestamp as u64; Ok(Quote {
if edge.pool.open_time > now_ts { in_amount: 0,
return Ok(Quote { out_amount: 0,
in_amount: u64::MAX, fee_amount: 0,
out_amount: 0, fee_mint: id.mint_a,
fee_amount: 0, })
fee_mint: edge.pool.token_0_mint,
});
}
let quote = if id.is_a_to_b {
let result = swap_base_output(
&edge.pool,
&edge.config,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
out_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_0_mint,
}
} else {
let result = swap_base_output(
&edge.pool,
&edge.config,
edge.pool.token_1_vault,
edge.vault_1_amount,
&edge.mint_1,
edge.pool.token_0_vault,
edge.vault_0_amount,
&edge.mint_0,
out_amount,
)?;
Quote {
in_amount: result.0,
out_amount: result.1,
fee_amount: result.2,
fee_mint: edge.pool.token_1_mint,
}
};
Ok(quote)
} }
} }
async fn fetch_raydium_account<T: Discriminator + AccountDeserialize>( async fn fetch_gobbler_pools(
rpc: &mut RouterRpcClient, rpc: &mut RouterRpcClient,
program_id: Pubkey, ) -> anyhow::Result<Vec<(Pubkey, PoolState)>> {
len: usize,
) -> anyhow::Result<Vec<(Pubkey, T)>> {
let config = RpcProgramAccountsConfig { let config = RpcProgramAccountsConfig {
filters: Some(vec![ filters: Some(vec![
RpcFilterType::DataSize(len as u64), RpcFilterType::DataSize(PoolState::LEN as u64),
RpcFilterType::Memcmp(Memcmp::new_raw_bytes(0, T::DISCRIMINATOR.to_vec())), RpcFilterType::Memcmp(Memcmp::new_raw_bytes(0, PoolState::DISCRIMINATOR.to_vec())),
]), ]),
account_config: RpcAccountInfoConfig { account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64), encoding: Some(UiAccountEncoding::Base64),
@ -386,16 +367,17 @@ async fn fetch_raydium_account<T: Discriminator + AccountDeserialize>(
}; };
let snapshot = rpc let snapshot = rpc
.get_program_accounts_with_config(&program_id, config) .get_program_accounts_with_config(&raydium_cp_swap::id(), config)
.await?; .await?;
let result = snapshot let result = snapshot
.iter() .into_iter()
.map(|account| { .map(|account| {
let pool: T = T::try_deserialize(&mut account.data.as_slice()).unwrap(); let pool: PoolState =
PoolState::try_deserialize(&mut account.data.as_slice()).unwrap();
(account.pubkey, pool) (account.pubkey, pool)
}) })
.collect_vec(); .collect::<Vec<_>>();
Ok(result) Ok(result)
} }

View File

@ -1,13 +1,17 @@
use crate::edge::GobblerEdgeIdentifier; use crate::edge::{GobblerEdgeIdentifier, Operation, Direction};
use anchor_lang::{AccountDeserialize, Id, InstructionData, ToAccountMetas}; use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas};
use anchor_spl::associated_token::get_associated_token_address; use anchor_spl::associated_token::get_associated_token_address;
use raydium_cp_swap::AUTH_SEED;
use raydium_cp_swap::instruction::{Deposit, Withdraw, SwapBaseInput};
use raydium_cp_swap::program::RaydiumCpSwap; use raydium_cp_swap::program::RaydiumCpSwap;
use raydium_cp_swap::states::PoolState; use raydium_cp_swap::states::PoolState;
use raydium_cp_swap::AUTH_SEED;
use router_lib::dex::{AccountProviderView, SwapInstruction}; use router_lib::dex::{AccountProviderView, SwapInstruction};
use solana_program::instruction::Instruction; use solana_program::instruction::Instruction;
use solana_program::pubkey::Pubkey; use solana_program::pubkey::Pubkey;
use solana_sdk::account::ReadableAccount; use solana_sdk::account::ReadableAccount;
use spl_memo::id as spl_memo_id;
use spl_token::id as spl_token_id;
use spl_token_2022::id as spl_token_2022_id;
pub fn build_swap_ix( pub fn build_swap_ix(
id: &GobblerEdgeIdentifier, id: &GobblerEdgeIdentifier,
@ -24,58 +28,200 @@ pub fn build_swap_ix(
let other_amount_threshold = let other_amount_threshold =
((out_amount as f64 * (10_000f64 - max_slippage_bps as f64)) / 10_000f64).floor() as u64; ((out_amount as f64 * (10_000f64 - max_slippage_bps as f64)) / 10_000f64).floor() as u64;
let (input_token_mint, output_token_mint) = if id.is_a_to_b { match id.operation {
(pool.token_0_mint, pool.token_1_mint) Operation::Swap => {
} else { // Handle swap operation
(pool.token_1_mint, pool.token_0_mint) let (input_token_mint, output_token_mint) = match id.direction {
}; Direction::AtoB => (pool.token_0_mint, pool.token_1_mint),
let (input_token_program, output_token_program) = if id.is_a_to_b { Direction::BtoA => (pool.token_1_mint, pool.token_0_mint),
(pool.token_0_program, pool.token_1_program) _ => return Err(anyhow::anyhow!("Invalid direction for swap operation")),
} else { };
(pool.token_1_program, pool.token_0_program)
}; let (input_token_program, output_token_program) = match id.direction {
let (input_vault, output_vault) = if id.is_a_to_b { Direction::AtoB => (pool.token_0_program, pool.token_1_program),
(pool.token_0_vault, pool.token_1_vault) Direction::BtoA => (pool.token_1_program, pool.token_0_program),
} else { _ => return Err(anyhow::anyhow!("Invalid direction for swap operation")),
(pool.token_1_vault, pool.token_0_vault) };
let (input_vault, output_vault) = match id.direction {
Direction::AtoB => (pool.token_0_vault, pool.token_1_vault),
Direction::BtoA => (pool.token_1_vault, pool.token_0_vault),
_ => return Err(anyhow::anyhow!("Invalid direction for swap operation")),
};
let (input_token_account, output_token_account) = (
get_associated_token_address(wallet_pk, &input_token_mint),
get_associated_token_address(wallet_pk, &output_token_mint),
);
let instruction = SwapBaseInput {
amount_in: amount,
minimum_amount_out: other_amount_threshold,
};
let (authority, __bump) =
Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id());
let accounts = raydium_cp_swap::accounts::Swap {
payer: *wallet_pk,
authority,
amm_config: pool.amm_config,
pool_state: id.pool,
input_token_account,
output_token_account,
input_vault,
output_vault,
input_token_program,
output_token_program,
input_token_mint,
output_token_mint,
observation_state: pool.observation_key,
};
let result = SwapInstruction {
instruction: Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
},
out_pubkey: output_token_account,
out_mint: output_token_mint,
in_amount_offset: 8,
cu_estimate: Some(40_000),
};
Ok(result)
}
Operation::Deposit => build_deposit_ix(
id,
chain_data,
wallet_pk,
in_amount,
max_slippage_bps,
),
Operation::Withdraw => build_withdraw_ix(
id,
chain_data,
wallet_pk,
in_amount,
max_slippage_bps,
),
}
}
pub fn build_deposit_ix(
id: &GobblerEdgeIdentifier,
chain_data: &AccountProviderView,
wallet_pk: &Pubkey,
lp_amount: u64,
max_slippage_bps: i32,
) -> anyhow::Result<SwapInstruction> {
let pool_account = chain_data.account(&id.pool)?;
let pool = PoolState::try_deserialize(&mut pool_account.account.data())?;
let (token_a_mint, token_b_mint) = (pool.token_0_mint, pool.token_1_mint);
let (vault_a, vault_b) = (pool.token_0_vault, pool.token_1_vault);
let user_token_a_account = get_associated_token_address(wallet_pk, &token_a_mint);
let user_token_b_account = get_associated_token_address(wallet_pk, &token_b_mint);
let user_lp_token_account = get_associated_token_address(wallet_pk, &pool.lp_mint);
// You may need to calculate maximum amounts based on slippage
let instruction = Deposit {
lp_token_amount: lp_amount,
maximum_token_0_amount: u64::MAX, // Adjust as needed
maximum_token_1_amount: u64::MAX, // Adjust as needed
}; };
let (input_token_account, output_token_account) = (
get_associated_token_address(wallet_pk, &input_token_mint),
get_associated_token_address(wallet_pk, &output_token_mint),
);
let instruction = raydium_cp_swap::instruction::SwapBaseInput {
amount_in: amount,
minimum_amount_out: other_amount_threshold,
};
let (authority, __bump) = let (authority, __bump) =
Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id()); Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id());
let accounts = raydium_cp_swap::accounts::Swap { let accounts = raydium_cp_swap::accounts::Deposit {
payer: *wallet_pk, owner: *wallet_pk,
authority, authority,
amm_config: pool.amm_config,
pool_state: id.pool, pool_state: id.pool,
input_token_account, owner_lp_token: user_lp_token_account,
output_token_account, token_0_account: user_token_a_account,
input_vault, token_1_account: user_token_b_account,
output_vault, token_0_vault: vault_a,
input_token_program, token_1_vault: vault_b,
output_token_program, token_program: spl_token_id(),
input_token_mint, token_program_2022: spl_token_2022_id(),
output_token_mint, vault_0_mint: token_a_mint,
observation_state: pool.observation_key, vault_1_mint: token_b_mint,
lp_mint: pool.lp_mint,
};
let deposit_instruction = Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
}; };
let result = SwapInstruction { let result = SwapInstruction {
instruction: Instruction { instruction: deposit_instruction,
program_id: raydium_cp_swap::id(), out_pubkey: user_lp_token_account,
accounts: accounts.to_account_metas(None), out_mint: pool.lp_mint,
data: instruction.data(), in_amount_offset: 8,
}, cu_estimate: Some(40_000),
out_pubkey: output_token_account, };
out_mint: output_token_mint,
Ok(result)
}
pub fn build_withdraw_ix(
id: &GobblerEdgeIdentifier,
chain_data: &AccountProviderView,
wallet_pk: &Pubkey,
lp_amount: u64,
max_slippage_bps: i32,
) -> anyhow::Result<SwapInstruction> {
let pool_account = chain_data.account(&id.pool)?;
let pool = PoolState::try_deserialize(&mut pool_account.account.data())?;
let (token_a_mint, token_b_mint) = (pool.token_0_mint, pool.token_1_mint);
let (vault_a, vault_b) = (pool.token_0_vault, pool.token_1_vault);
let user_token_a_account = get_associated_token_address(wallet_pk, &token_a_mint);
let user_token_b_account = get_associated_token_address(wallet_pk, &token_b_mint);
let user_lp_token_account = get_associated_token_address(wallet_pk, &pool.lp_mint);
// You may need to calculate minimum amounts based on slippage
let instruction = Withdraw {
lp_token_amount: lp_amount,
minimum_token_0_amount: 0, // Adjust as needed
minimum_token_1_amount: 0, // Adjust as needed
};
let (authority, __bump) =
Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &raydium_cp_swap::id());
let accounts = raydium_cp_swap::accounts::Withdraw {
owner: *wallet_pk,
authority,
pool_state: id.pool,
owner_lp_token: user_lp_token_account,
token_0_account: user_token_a_account,
token_1_account: user_token_b_account,
token_0_vault: vault_a,
token_1_vault: vault_b,
token_program: spl_token_id(),
token_program_2022: spl_token_2022_id(),
vault_0_mint: token_a_mint,
vault_1_mint: token_b_mint,
memo_program: spl_memo_id(),
lp_mint: pool.lp_mint,
};
let withdraw_instruction = Instruction {
program_id: raydium_cp_swap::id(),
accounts: accounts.to_account_metas(None),
data: instruction.data(),
};
let result = SwapInstruction {
instruction: withdraw_instruction,
out_pubkey: user_token_a_account, // Adjust as needed
out_mint: token_a_mint,
in_amount_offset: 8, in_amount_offset: 8,
cu_estimate: Some(40_000), cu_estimate: Some(40_000),
}; };