From 0e35a511959fe4535d3bee41071ec6e1e1059d5d Mon Sep 17 00:00:00 2001 From: billythedummy Date: Fri, 28 Feb 2025 18:37:22 +0800 Subject: [PATCH 1/3] prefund_withdraw_stake --- common/src/withdraw_stake.rs | 4 + jup_interface/src/pool_pair/common.rs | 28 +++++-- stakedex_sdk/src/lib.rs | 111 +++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 17 deletions(-) diff --git a/common/src/withdraw_stake.rs b/common/src/withdraw_stake.rs index 1ef0c24..f3b0531 100644 --- a/common/src/withdraw_stake.rs +++ b/common/src/withdraw_stake.rs @@ -37,6 +37,10 @@ impl WithdrawStakeQuote { self.lamports_out == 0 } + pub fn is_rent_exempt(&self) -> bool { + self.lamports_out.saturating_sub(self.lamports_staked) >= STAKE_ACCOUNT_RENT_EXEMPT_LAMPORTS + } + pub fn from_lamports_and_voter(stake_acc_lamports: u64, voter: Pubkey) -> Self { let (lamports_out, lamports_staked) = if stake_acc_lamports > STAKE_ACCOUNT_RENT_EXEMPT_LAMPORTS { diff --git a/jup_interface/src/pool_pair/common.rs b/jup_interface/src/pool_pair/common.rs index 9b1c78d..5216f2b 100644 --- a/jup_interface/src/pool_pair/common.rs +++ b/jup_interface/src/pool_pair/common.rs @@ -245,14 +245,12 @@ pub fn first_avail_prefund_quote WithdrawStakeQuote { +/// Returns the state of the stake account with rent-exempt lamports prefunded. +/// +/// This is basically adding [`STAKE_ACCOUNT_RENT_EXEMPT_LAMPORTS`] non-staked lamports to the stake account +/// while treating its original [`WithdrawStakeQuote::lamports_out`] as the [`WithdrawStakeQuote::lamports_staked`] +pub fn prefund_transform_wsq(mut wsq: WithdrawStakeQuote) -> WithdrawStakeQuote { wsq.lamports_staked = wsq.lamports_out; wsq.lamports_out += STAKE_ACCOUNT_RENT_EXEMPT_LAMPORTS; wsq } +/// Returns the state of the stake account after `prefund_split_lamports` is split +/// from it to repay the prefund flash loan +pub fn wsq_post_prefund_repay( + mut wsq: WithdrawStakeQuote, + prefund_split_lamports: u64, +) -> WithdrawStakeQuote { + wsq.lamports_out = wsq.lamports_out.saturating_sub(prefund_split_lamports); + wsq.lamports_staked = wsq.lamports_staked.saturating_sub(prefund_split_lamports); + wsq +} + /// Calculates approximate fees charged in terms of out token given known /// amt_after_fee in terms of out token and fee ratio fn approx_fees_charged_out_token(amt_after_fee: u64, fee_num: u64, fee_denom: u64) -> Result { diff --git a/stakedex_sdk/src/lib.rs b/stakedex_sdk/src/lib.rs index f667d10..f5f84eb 100644 --- a/stakedex_sdk/src/lib.rs +++ b/stakedex_sdk/src/lib.rs @@ -3,11 +3,11 @@ use std::collections::HashSet; use anyhow::{anyhow, Result}; use itertools::Itertools; use jupiter_amm_interface::{ - AccountMap, Amm, AmmContext, KeyedAccount, Quote, QuoteParams, SwapParams, + AccountMap, Amm, AmmContext, KeyedAccount, Quote, QuoteParams, SwapMode, SwapParams, }; use lazy_static::lazy_static; use sanctum_lst_list::{PoolInfo, SanctumLst}; -use solana_sdk::{instruction::Instruction, pubkey::Pubkey, system_program}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, stake, system_program, sysvar}; use spl_token::native_mint; use stakedex_interface::{ DepositStakeKeys, PrefundSwapViaStakeIxArgs, PrefundSwapViaStakeKeys, @@ -15,20 +15,23 @@ use stakedex_interface::{ StakeWrappedSolKeys, SwapViaStakeArgs, WithdrawWrappedSolIxArgs, WithdrawWrappedSolKeys, }; use stakedex_jup_interface::{ - manual_concat_get_account_metas, prefund_get_account_metas, quote_pool_pair, DepositSolWrapper, - DepositWithdrawSolWrapper, OneWayPoolPair, PrefundRepayParams, TwoWayPoolPair, + manual_concat_get_account_metas, prefund_get_account_metas, prefund_transform_wsq, + quote_pool_pair, wsq_post_prefund_repay, DepositSolWrapper, DepositWithdrawSolWrapper, + OneWayPoolPair, PrefundRepayParams, TwoWayPoolPair, }; use stakedex_lido::LidoStakedex; use stakedex_marinade::MarinadeStakedex; use stakedex_sdk_common::{ - find_fee_token_acc, lido_state, marinade_state, msol, + find_bridge_stake, find_fee_token_acc, lido_state, marinade_state, msol, + slumdog_stake_create_with_seed, stakedex_program::{self, WSOL_FEE_TOKEN_ACCOUNT_ID}, - stsol, unstake_it_program, wsol, wsol_bridge_in, BaseStakePoolAmm, DepositSol, DepositStake, - DepositStakeInfo, DepositStakeQuote, InitFromKeyedAccount, WithdrawSol, WithdrawStake, - WithdrawStakeQuote, DEPOSIT_STAKE_DST_TOKEN_ACCOUNT_INDEX, + stsol, unstake_it_pool, unstake_it_program, wsol, wsol_bridge_in, BaseStakePoolAmm, DepositSol, + DepositStake, DepositStakeInfo, DepositStakeQuote, InitFromKeyedAccount, WithdrawSol, + WithdrawStake, WithdrawStakeQuote, WithdrawStakeQuoteErr, + DEPOSIT_STAKE_DST_TOKEN_ACCOUNT_INDEX, }; use stakedex_spl_stake_pool::{SplStakePoolStakedex, SplStakePoolStakedexWithWithdrawSol}; -use stakedex_unstake_it::{UnstakeItStakedex, UnstakeItStakedexPrefund}; +use stakedex_unstake_it::{find_stake_account_record, UnstakeItStakedex, UnstakeItStakedexPrefund}; pub use sanctum_lst_list::SanctumLstList; pub use stakedex_interface::ID as stakedex_program_id; @@ -300,6 +303,8 @@ impl Stakedex { ) } + /// Returns [PrefundWithdrawStake, DepositStake] instructions + /// to create a SwapViaStake action without running into CPI account write-lock limits pub fn manual_concat_prefund_swap_via_stake_ixs( &self, swap_params: &SwapParams, @@ -564,6 +569,94 @@ impl Stakedex { Ok(ix) } + /// `output_mint` can either be + /// - the desired vote account of the stake account to withdraw, or + /// - the stake program to indicate no preference of voter + pub fn quote_prefund_withdraw_stake( + &self, + quote_params: &QuoteParams, + ) -> Result { + let prefund_split_lamports = self.prefund_repay_params().prefund_split_lamports()?; + let withdraw_from = self + .get_withdraw_stake_pool("e_params.input_mint) + .ok_or_else(|| anyhow!("pool not found for input mint {}", quote_params.input_mint))?; + if !withdraw_from.can_accept_stake_withdrawals() { + return Err(WithdrawStakeQuoteErr::CannotAcceptStakeWithdrawals.into()); + } + let itr = withdraw_from.withdraw_stake_quote_iter_dyn(quote_params.amount); + + for wsq in itr { + let wsq = prefund_transform_wsq(wsq); + let wsq_after_repaying_prefund = wsq_post_prefund_repay(wsq, prefund_split_lamports); + if wsq_after_repaying_prefund.is_rent_exempt() + && (quote_params.output_mint == wsq.voter + || quote_params.output_mint == stake::program::ID) + { + return Ok(wsq_after_repaying_prefund); + } + } + + Err(anyhow!("No route found")) + } + + /// `destination_mint` can either be + /// - the desired vote account of the stake account to withdraw, or + /// - the stake program to indicate no preference of voter + pub fn prefund_withdraw_stake_ix( + &self, + SwapParams { + in_amount, + source_mint, + destination_mint, + source_token_account, + token_transfer_authority, + .. + }: &SwapParams, + bridge_stake_seed: u32, + ) -> Result { + let withdraw_from = self + .get_withdraw_stake_pool(source_mint) + .ok_or_else(|| anyhow!("pool not found for input mint {}", source_mint))?; + let wsq = self.quote_prefund_withdraw_stake(&QuoteParams { + amount: *in_amount, + input_mint: *source_mint, + output_mint: *destination_mint, + swap_mode: SwapMode::ExactIn, + })?; + let bridge_stake_seed_le_bytes = bridge_stake_seed.to_le_bytes(); + let bridge_stake = + find_bridge_stake(token_transfer_authority, &bridge_stake_seed_le_bytes).0; + let slumdog_stake = slumdog_stake_create_with_seed(&bridge_stake)?; + let mut ix = stakedex_interface::prefund_withdraw_stake_ix( + PrefundWithdrawStakeKeys { + user: *token_transfer_authority, + src_token_from: *source_token_account, + bridge_stake, + src_token_mint: *source_mint, + prefunder: stakedex_program::PREFUNDER_ID, + slumdog_stake, + unstakeit_program: unstake_it_program::ID, + unstake_pool: unstake_it_pool::ID, + pool_sol_reserves: unstake_it_program::SOL_RESERVES_ID, + unstake_fee: unstake_it_program::FEE_ID, + slumdog_stake_acc_record: find_stake_account_record(&slumdog_stake).0, + unstake_protocol_fee: unstake_it_program::PROTOCOL_FEE_ID, + unstake_protocol_fee_dest: self.prefund_repay_params().protocol_fee_dest, + clock: sysvar::clock::ID, + stake_program: stake::program::ID, + system_program: system_program::ID, + }, + PrefundWithdrawStakeIxArgs { + args: SwapViaStakeArgs { + amount: *in_amount, + bridge_stake_seed, + }, + }, + )?; + ix.accounts.extend(withdraw_from.virtual_ix(&wsq)?.accounts); + Ok(ix) + } + /// Creates all possible Amms from the underlying available Stakedexes pub fn get_amms(self) -> Vec> { #[derive(Clone)] From 439051f2f58cdbd8c5a9b46deda457541d37ec14 Mon Sep 17 00:00:00 2001 From: billythedummy Date: Fri, 28 Feb 2025 18:44:35 +0800 Subject: [PATCH 2/3] loosen borsh dep req --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2192b38..7e0e2ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ [workspace.dependencies] anyhow = "^1.0" bincode = "^1.0" -borsh = "^1" +borsh = ">=0.9" clap = "^4" itertools = ">=0.1" jupiter-amm-interface = "~0.4.0" From d4e6d224a0447d0e7cf627888d58924504166348 Mon Sep 17 00:00:00 2001 From: billythedummy Date: Sun, 2 Mar 2025 14:55:42 +0800 Subject: [PATCH 3/3] it was dependabot all along --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9e3a38..f216948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -553,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" dependencies = [ "borsh-derive 0.10.4", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -4393,8 +4393,7 @@ dependencies = [ [[package]] name = "spl-stake-pool" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2b2908579aefbdb1ed3e941252cab09e7e471e5b52efd5ecc54d20829ef5e8" +source = "git+https://github.com/igneous-labs/stake-pool.git?branch=loosen-deps#27eecce9ac698a3a9109cb8b225079455cef8d4c" dependencies = [ "arrayref", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 7e0e2ce..933b2b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ spl-associated-token-account = { version = ">=1", features = ["no-entrypoint"] } # but 2.0.1 patches those breaking changes. # Ideally, this vers requirement should be "anything >=1.0 except 2.0.0" # but theres no way to express that in cargo dep vers syntax -spl-stake-pool = { version = ">=1.0", features = ["no-entrypoint"] } +spl-stake-pool = { git = "https://github.com/igneous-labs/stake-pool.git", branch = "loosen-deps", features = ["no-entrypoint"] } spl-token = ">=3.0" thiserror = "^1.0" tokio = "^1.0"