diff --git a/Cargo.lock b/Cargo.lock index b89aa6c..095af7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1867,11 +1867,14 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "marinade_finance_interface" -version = "0.0.0" -source = "git+https://github.com/igneous-labs/marinade_finance_interface?rev=d625979#d625979d145831f9261f76777501d0037e83fd9d" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/marinade_finance_interface?rev=4d1895b#4d1895b6b1c34d731c1589e6d8a6c8feb6258152" dependencies = [ "borsh 0.9.3", + "num-derive 0.3.3", + "num-traits", "solana-program", + "thiserror", ] [[package]] @@ -3887,10 +3890,12 @@ dependencies = [ "borsh 0.9.3", "marinade_finance_interface", "solana-program", + "solana-sdk", "spl-token", "stakedex_deposit_sol_interface", "stakedex_deposit_stake_interface", "stakedex_sdk_common", + "stakedex_withdraw_stake_interface", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 215697e..7ac8a45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ bincode = "^1.0" borsh = "^0.9" jupiter-amm-interface = "~0.3.2" lazy_static = "^1.0" +lido = { git = "https://github.com/igneous-labs/solido", branch = "mod/2.0.0-loose-deps", features = ["no-entrypoint"] } +marinade_finance_interface = { git = "https://github.com/igneous-labs/marinade_finance_interface", rev = "4d1895b" } num-derive = ">=0.1" num-traits = ">=0.1" rust_decimal = "^1.0" diff --git a/common/src/address/marinade.rs b/common/src/address/marinade.rs index e2839bf..70bfcac 100644 --- a/common/src/address/marinade.rs +++ b/common/src/address/marinade.rs @@ -7,6 +7,8 @@ pub mod marinade_program { ("liq_pool_msol_leg_authority", b"u\x11\x9b1u\x80u\x86\xe3\xf4\xa7\xe5\xcd\x0f\x89\x0e\x96\xa7S\xb1\x0f\xcc\xc7h\x1e\x94s\xa0\x082p\xf1", b"liq_st_sol_authority"), ("msol_mint_auth", b"u\x11\x9b1u\x80u\x86\xe3\xf4\xa7\xe5\xcd\x0f\x89\x0e\x96\xa7S\xb1\x0f\xcc\xc7h\x1e\x94s\xa0\x082p\xf1", b"st_mint"), ("reserve", b"u\x11\x9b1u\x80u\x86\xe3\xf4\xa7\xe5\xcd\x0f\x89\x0e\x96\xa7S\xb1\x0f\xcc\xc7h\x1e\x94s\xa0\x082p\xf1", b"reserve"), + ("stake_withdraw_auth", b"u\x11\x9b1u\x80u\x86\xe3\xf4\xa7\xe5\xcd\x0f\x89\x0e\x96\xa7S\xb1\x0f\xcc\xc7h\x1e\x94s\xa0\x082p\xf1", b"withdraw"), + ("stake_deposit_auth", b"u\x11\x9b1u\x80u\x86\xe3\xf4\xa7\xe5\xcd\x0f\x89\x0e\x96\xa7S\xb1\x0f\xcc\xc7h\x1e\x94s\xa0\x082p\xf1", b"deposit"), ] ); } diff --git a/common/src/pool_pair.rs b/common/src/pool_pair.rs index 56218b1..6d2e43b 100644 --- a/common/src/pool_pair.rs +++ b/common/src/pool_pair.rs @@ -14,9 +14,10 @@ use crate::{ account_missing_err, apply_global_fee, find_bridge_stake, find_fee_token_acc, find_stake_pool_pair_amm_key, jupiter_stakedex_interface::{Swap, STAKEDEX_ACCOUNT_META}, - DepositStake, DepositStakeInfo, DepositStakeQuote, SwapViaStakeQuoteErr, WithdrawStake, - WithdrawStakeQuote, WithdrawStakeQuoteErr, SWAP_VIA_STAKE_DST_TOKEN_MINT_ACCOUNT_INDEX, - SWAP_VIA_STAKE_SRC_TOKEN_MINT_ACCOUNT_INDEX, TEMPORARY_JUP_AMM_LABEL, + marinade_state, DepositStake, DepositStakeInfo, DepositStakeQuote, SwapViaStakeQuoteErr, + WithdrawStake, WithdrawStakeQuote, WithdrawStakeQuoteErr, + SWAP_VIA_STAKE_DST_TOKEN_MINT_ACCOUNT_INDEX, SWAP_VIA_STAKE_SRC_TOKEN_MINT_ACCOUNT_INDEX, + TEMPORARY_JUP_AMM_LABEL, }; pub fn first_avail_quote( @@ -331,4 +332,12 @@ where fn program_id(&self) -> Pubkey { stakedex_interface::ID } + + /// Because marinade doesn't provide an easy way to match their stake accounts + /// to the validator vote account, we have to fetch all their stake accounts on + /// update to figure this out + fn has_dynamic_accounts(&self) -> bool { + self.p1.main_state_key() == marinade_state::ID + || self.p2.main_state_key() == marinade_state::ID + } } diff --git a/interfaces/stakedex_interface/idl.json b/interfaces/stakedex_interface/idl.json index c49df8d..43c005f 100644 --- a/interfaces/stakedex_interface/idl.json +++ b/interfaces/stakedex_interface/idl.json @@ -61,7 +61,8 @@ { "name": "systemProgram", "isMut": false, - "isSigner": false + "isSigner": false, + "desc": "System program. The deposit SOL accounts slice follows." } ], "args": [ @@ -82,7 +83,7 @@ "name": "user", "isMut": true, "isSigner": true, - "desc": "The authority of src_token_from. Needs to be mutable to support marinde deposit stake." + "desc": "The authority of src_token_from. Needs to be mutable to support marinade deposit stake." }, { "name": "srcTokenFrom", @@ -118,17 +119,15 @@ "name": "destTokenMint", "isMut": true, "isSigner": false, - "desc": "Output token mint. If this is wrapped SOL, the account can be set to read-only" + "desc": "Output token mint. If this is wrapped SOL, the account can be set to read-only. The withdraw stake and deposit stake accounts slices follow." } ], "args": [ { - "name": "amount", - "type": "u64" - }, - { - "name": "bridgeStakeSeed", - "type": "u32" + "name": "args", + "type": { + "defined": "SwapViaStakeArgs" + } } ], "discriminant": { @@ -280,7 +279,7 @@ "name": "destTokenMint", "isMut": true, "isSigner": false, - "desc": "Output token mint. If this is wrapped SOL, the account can be set to read-only" + "desc": "Output token mint. If this is wrapped SOL, the account can be set to read-only. The deposit stake accounts slice follows." } ], "args": [], @@ -288,6 +287,292 @@ "type": "u8", "value": 5 } + }, + { + "name": "SlumSwapViaStake", + "accounts": [ + { + "name": "user", + "isMut": true, + "isSigner": true, + "desc": "The authority of src_token_from. Needs to be mutable to support marinade deposit stake." + }, + { + "name": "srcTokenFrom", + "isMut": true, + "isSigner": false, + "desc": "The token account to swap src tokens from" + }, + { + "name": "destTokenTo", + "isMut": true, + "isSigner": false, + "desc": "The token account to receive dest tokens to" + }, + { + "name": "bridgeStake", + "isMut": true, + "isSigner": false, + "desc": "The bridge stake account thats withdrawn then deposited. PDA. seeds = ['bridge_stake', user.pubkey, SwapArgs.bridge_stake_seed]. Might be long-lived, make sure the seed is not already in use" + }, + { + "name": "destTokenFeeTokenAccount", + "isMut": true, + "isSigner": false, + "desc": "The dest_token_mint token account collecting fees. PDA. Seeds = ['fee', dest_token_mint.pubkey]" + }, + { + "name": "srcTokenMint", + "isMut": true, + "isSigner": false, + "desc": "Input token mint. If this is wrapped SOL, the account can be set to read-only" + }, + { + "name": "destTokenMint", + "isMut": true, + "isSigner": false, + "desc": "Output token mint. If this is wrapped SOL, the account can be set to read-only" + }, + { + "name": "slumdogStake", + "isMut": true, + "isSigner": false, + "desc": "The slumdog stake account is split from bridge_stake upon stake withdraw and instant unstaked to repay slumlord's flash loan. create_with_seed(bridge_stake.pubkey, 'slumdog', stake_program). Might be long-lived, but should be not in use as long as bridge_stake is not in use" + }, + { + "name": "slumlord", + "isMut": true, + "isSigner": false, + "desc": "The slumlord PDA to repay the flash loan to" + }, + { + "name": "slumlord_program", + "isMut": false, + "isSigner": false, + "desc": "The slumlord program ID" + }, + { + "name": "instructions", + "isMut": false, + "isSigner": false, + "desc": "instructions sysvar" + }, + { + "name": "unstakeitProgram", + "isMut": false, + "isSigner": false, + "desc": "Sanctum unstake program. unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ" + }, + { + "name": "unstakePool", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool. FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd" + }, + { + "name": "poolSolReserves", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool SOL reserves. 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ" + }, + { + "name": "unstakeFee", + "isMut": false, + "isSigner": false, + "desc": "Sanctum unstake pool Fee account. 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY" + }, + { + "name": "slumdogStakeAccRecord", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool stake account record for slumdog stake. PDA of sanctum unstake program. Seeds = [unstakePool.pubkey, slumdogStake.pubkey]." + }, + { + "name": "unstakeProtocolFee", + "isMut": false, + "isSigner": false, + "desc": "Sanctum unstake pool protocol fee account. 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU" + }, + { + "name": "unstakeProtocolFeeDest", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool protocol fee destination. unstakeProtocolFee.destination" + }, + { + "name": "clock", + "isMut": false, + "isSigner": false, + "desc": "sysvar clock" + }, + { + "name": "stakeProgram", + "isMut": false, + "isSigner": false, + "desc": "stake program" + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "desc": "System program. The withdraw stake and deposit stake accounts slices follow." + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": "SwapViaStakeArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 6 + } + }, + { + "name": "SlumWithdrawStake", + "accounts": [ + { + "name": "user", + "isMut": true, + "isSigner": true, + "desc": "The withdraw authority of stake_account. Needs to be mutable and system account to receive slumlord flash loan." + }, + { + "name": "srcTokenFrom", + "isMut": true, + "isSigner": false, + "desc": "The token account to burn src tokens from in order to withdraw stake" + }, + { + "name": "bridgeStake", + "isMut": true, + "isSigner": false, + "desc": "The bridge stake account thats withdrawn and given to the user. PDA. seeds = ['bridge_stake', user.pubkey, SwapArgs.bridge_stake_seed]. Might be long-lived, make sure the seed is not already in use" + }, + { + "name": "srcTokenMint", + "isMut": true, + "isSigner": false, + "desc": "Input LST token mint" + }, + { + "name": "slumdogStake", + "isMut": true, + "isSigner": false, + "desc": "The slumdog stake account is split from bridge_stake upon stake withdraw and instant unstaked to repay slumlord's flash loan. create_with_seed(bridge_stake.pubkey, 'slumdog', stake_program). Might be long-lived, but should be not in use as long as bridge_stake is not in use" + }, + { + "name": "slumlord", + "isMut": true, + "isSigner": false, + "desc": "The slumlord PDA to repay the flash loan to" + }, + { + "name": "slumlord_program", + "isMut": false, + "isSigner": false, + "desc": "The slumlord program ID" + }, + { + "name": "instructions", + "isMut": false, + "isSigner": false, + "desc": "instructions sysvar" + }, + { + "name": "unstakeitProgram", + "isMut": false, + "isSigner": false, + "desc": "Sanctum unstake program. unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ" + }, + { + "name": "unstakePool", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool. FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd" + }, + { + "name": "poolSolReserves", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool SOL reserves. 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ" + }, + { + "name": "unstakeFee", + "isMut": false, + "isSigner": false, + "desc": "Sanctum unstake pool Fee account. 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY" + }, + { + "name": "slumdogStakeAccRecord", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool stake account record for slumdog stake. PDA of sanctum unstake program. Seeds = [unstakePool.pubkey, slumdogStake.pubkey]." + }, + { + "name": "unstakeProtocolFee", + "isMut": false, + "isSigner": false, + "desc": "Sanctum unstake pool protocol fee account. 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU" + }, + { + "name": "unstakeProtocolFeeDest", + "isMut": true, + "isSigner": false, + "desc": "Sanctum unstake pool protocol fee destination. unstakeProtocolFee.destination" + }, + { + "name": "clock", + "isMut": false, + "isSigner": false, + "desc": "sysvar clock" + }, + { + "name": "stakeProgram", + "isMut": false, + "isSigner": false, + "desc": "stake program" + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "desc": "System program. The withdraw stake accounts slices follow." + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": "SwapViaStakeArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 7 + } + } + ], + "types": [ + { + "name": "SwapViaStakeArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "bridgeStakeSeed", + "type": "u32" + } + ] + } } ], "errors": [ @@ -365,6 +650,21 @@ "code": 14, "name": "UnreachableError", "msg": "If you see this, there's a serious bug somewhere" + }, + { + "code": 15, + "name": "WrongSlumlordProgram", + "msg": "Wrong slumlord program" + }, + { + "code": 16, + "name": "WrongSlumlordAccount", + "msg": "Wrong slumlord PDA" + }, + { + "code": 17, + "name": "SlumdogUnstakeTooSmall", + "msg": "Instant unstake of slumdog stake was not enough to cover slumlord flash loan" } ], "metadata": { diff --git a/interfaces/stakedex_interface/src/errors.rs b/interfaces/stakedex_interface/src/errors.rs index 6898f14..6ee1c39 100644 --- a/interfaces/stakedex_interface/src/errors.rs +++ b/interfaces/stakedex_interface/src/errors.rs @@ -36,6 +36,12 @@ pub enum StakedexError { UnsupportedProgram = 13, #[error("If you see this, there's a serious bug somewhere")] UnreachableError = 14, + #[error("Wrong slumlord program")] + WrongSlumlordProgram = 15, + #[error("Wrong slumlord PDA")] + WrongSlumlordAccount = 16, + #[error("Instant unstake of slumdog stake was not enough to cover slumlord flash loan")] + SlumdogUnstakeTooSmall = 17, } impl From for ProgramError { fn from(e: StakedexError) -> Self { diff --git a/interfaces/stakedex_interface/src/instructions.rs b/interfaces/stakedex_interface/src/instructions.rs index 60e806a..a40763c 100644 --- a/interfaces/stakedex_interface/src/instructions.rs +++ b/interfaces/stakedex_interface/src/instructions.rs @@ -1,3 +1,4 @@ +use crate::*; use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::AccountInfo, @@ -16,6 +17,8 @@ pub enum StakedexProgramIx { CloseFeeTokenAccount, WithdrawFees, DepositStake, + SlumSwapViaStake(SlumSwapViaStakeIxArgs), + SlumWithdrawStake(SlumWithdrawStakeIxArgs), } impl StakedexProgramIx { pub fn deserialize(buf: &[u8]) -> std::io::Result { @@ -34,6 +37,12 @@ impl StakedexProgramIx { CLOSE_FEE_TOKEN_ACCOUNT_IX_DISCM => Ok(Self::CloseFeeTokenAccount), WITHDRAW_FEES_IX_DISCM => Ok(Self::WithdrawFees), DEPOSIT_STAKE_IX_DISCM => Ok(Self::DepositStake), + SLUM_SWAP_VIA_STAKE_IX_DISCM => Ok(Self::SlumSwapViaStake( + SlumSwapViaStakeIxArgs::deserialize(&mut reader)?, + )), + SLUM_WITHDRAW_STAKE_IX_DISCM => Ok(Self::SlumWithdrawStake( + SlumWithdrawStakeIxArgs::deserialize(&mut reader)?, + )), _ => Err(std::io::Error::new( std::io::ErrorKind::Other, format!("discm {:?} not found", maybe_discm), @@ -54,6 +63,14 @@ impl StakedexProgramIx { Self::CloseFeeTokenAccount => writer.write_all(&[CLOSE_FEE_TOKEN_ACCOUNT_IX_DISCM]), Self::WithdrawFees => writer.write_all(&[WITHDRAW_FEES_IX_DISCM]), Self::DepositStake => writer.write_all(&[DEPOSIT_STAKE_IX_DISCM]), + Self::SlumSwapViaStake(args) => { + writer.write_all(&[SLUM_SWAP_VIA_STAKE_IX_DISCM])?; + args.serialize(&mut writer) + } + Self::SlumWithdrawStake(args) => { + writer.write_all(&[SLUM_WITHDRAW_STAKE_IX_DISCM])?; + args.serialize(&mut writer) + } } } pub fn try_to_vec(&self) -> std::io::Result> { @@ -82,6 +99,7 @@ pub struct StakeWrappedSolAccounts<'me, 'info> { ///wSOL token mint pub wsol_mint: &'me AccountInfo<'info>, pub token_program: &'me AccountInfo<'info>, + ///System program. The deposit SOL accounts slice follows. pub system_program: &'me AccountInfo<'info>, } #[derive(Copy, Clone, Debug)] @@ -103,6 +121,7 @@ pub struct StakeWrappedSolKeys { ///wSOL token mint pub wsol_mint: Pubkey, pub token_program: Pubkey, + ///System program. The deposit SOL accounts slice follows. pub system_program: Pubkey, } impl From> for StakeWrappedSolKeys { @@ -350,7 +369,7 @@ pub fn stake_wrapped_sol_verify_account_privileges<'me, 'info>( pub const SWAP_VIA_STAKE_IX_ACCOUNTS_LEN: usize = 7; #[derive(Copy, Clone, Debug)] pub struct SwapViaStakeAccounts<'me, 'info> { - ///The authority of src_token_from. Needs to be mutable to support marinde deposit stake. + ///The authority of src_token_from. Needs to be mutable to support marinade deposit stake. pub user: &'me AccountInfo<'info>, ///The token account to swap src tokens from pub src_token_from: &'me AccountInfo<'info>, @@ -362,12 +381,12 @@ pub struct SwapViaStakeAccounts<'me, 'info> { pub dest_token_fee_token_account: &'me AccountInfo<'info>, ///Input token mint. If this is wrapped SOL, the account can be set to read-only pub src_token_mint: &'me AccountInfo<'info>, - ///Output token mint. If this is wrapped SOL, the account can be set to read-only + ///Output token mint. If this is wrapped SOL, the account can be set to read-only. The withdraw stake and deposit stake accounts slices follow. pub dest_token_mint: &'me AccountInfo<'info>, } #[derive(Copy, Clone, Debug)] pub struct SwapViaStakeKeys { - ///The authority of src_token_from. Needs to be mutable to support marinde deposit stake. + ///The authority of src_token_from. Needs to be mutable to support marinade deposit stake. pub user: Pubkey, ///The token account to swap src tokens from pub src_token_from: Pubkey, @@ -379,7 +398,7 @@ pub struct SwapViaStakeKeys { pub dest_token_fee_token_account: Pubkey, ///Input token mint. If this is wrapped SOL, the account can be set to read-only pub src_token_mint: Pubkey, - ///Output token mint. If this is wrapped SOL, the account can be set to read-only + ///Output token mint. If this is wrapped SOL, the account can be set to read-only. The withdraw stake and deposit stake accounts slices follow. pub dest_token_mint: Pubkey, } impl From> for SwapViaStakeKeys { @@ -483,8 +502,7 @@ pub const SWAP_VIA_STAKE_IX_DISCM: u8 = 1u8; #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SwapViaStakeIxArgs { - pub amount: u64, - pub bridge_stake_seed: u32, + pub args: SwapViaStakeArgs, } #[derive(Clone, Debug, PartialEq)] pub struct SwapViaStakeIxData(pub SwapViaStakeIxArgs); @@ -1172,7 +1190,7 @@ pub struct DepositStakeAccounts<'me, 'info> { pub dest_token_to: &'me AccountInfo<'info>, ///The dest_token_mint token account collecting fees. PDA. Seeds = ['fee', dest_token_mint.pubkey] pub dest_token_fee_token_account: &'me AccountInfo<'info>, - ///Output token mint. If this is wrapped SOL, the account can be set to read-only + ///Output token mint. If this is wrapped SOL, the account can be set to read-only. The deposit stake accounts slice follows. pub dest_token_mint: &'me AccountInfo<'info>, } #[derive(Copy, Clone, Debug)] @@ -1185,7 +1203,7 @@ pub struct DepositStakeKeys { pub dest_token_to: Pubkey, ///The dest_token_mint token account collecting fees. PDA. Seeds = ['fee', dest_token_mint.pubkey] pub dest_token_fee_token_account: Pubkey, - ///Output token mint. If this is wrapped SOL, the account can be set to read-only + ///Output token mint. If this is wrapped SOL, the account can be set to read-only. The deposit stake accounts slice follows. pub dest_token_mint: Pubkey, } impl From> for DepositStakeKeys { @@ -1359,3 +1377,873 @@ pub fn deposit_stake_verify_account_privileges<'me, 'info>( } Ok(()) } +pub const SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN: usize = 21; +#[derive(Copy, Clone, Debug)] +pub struct SlumSwapViaStakeAccounts<'me, 'info> { + ///The authority of src_token_from. Needs to be mutable to support marinade deposit stake. + pub user: &'me AccountInfo<'info>, + ///The token account to swap src tokens from + pub src_token_from: &'me AccountInfo<'info>, + ///The token account to receive dest tokens to + pub dest_token_to: &'me AccountInfo<'info>, + ///The bridge stake account thats withdrawn then deposited. PDA. seeds = ['bridge_stake', user.pubkey, SwapArgs.bridge_stake_seed]. Might be long-lived, make sure the seed is not already in use + pub bridge_stake: &'me AccountInfo<'info>, + ///The dest_token_mint token account collecting fees. PDA. Seeds = ['fee', dest_token_mint.pubkey] + pub dest_token_fee_token_account: &'me AccountInfo<'info>, + ///Input token mint. If this is wrapped SOL, the account can be set to read-only + pub src_token_mint: &'me AccountInfo<'info>, + ///Output token mint. If this is wrapped SOL, the account can be set to read-only + pub dest_token_mint: &'me AccountInfo<'info>, + ///The slumdog stake account is split from bridge_stake upon stake withdraw and instant unstaked to repay slumlord's flash loan. create_with_seed(bridge_stake.pubkey, 'slumdog', stake_program). Might be long-lived, but should be not in use as long as bridge_stake is not in use + pub slumdog_stake: &'me AccountInfo<'info>, + ///The slumlord PDA to repay the flash loan to + pub slumlord: &'me AccountInfo<'info>, + ///The slumlord program ID + pub slumlord_program: &'me AccountInfo<'info>, + ///instructions sysvar + pub instructions: &'me AccountInfo<'info>, + ///Sanctum unstake program. unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ + pub unstakeit_program: &'me AccountInfo<'info>, + ///Sanctum unstake pool. FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd + pub unstake_pool: &'me AccountInfo<'info>, + ///Sanctum unstake pool SOL reserves. 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ + pub pool_sol_reserves: &'me AccountInfo<'info>, + ///Sanctum unstake pool Fee account. 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY + pub unstake_fee: &'me AccountInfo<'info>, + ///Sanctum unstake pool stake account record for slumdog stake. PDA of sanctum unstake program. Seeds = [unstakePool.pubkey, slumdogStake.pubkey]. + pub slumdog_stake_acc_record: &'me AccountInfo<'info>, + ///Sanctum unstake pool protocol fee account. 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU + pub unstake_protocol_fee: &'me AccountInfo<'info>, + ///Sanctum unstake pool protocol fee destination. unstakeProtocolFee.destination + pub unstake_protocol_fee_dest: &'me AccountInfo<'info>, + ///sysvar clock + pub clock: &'me AccountInfo<'info>, + ///stake program + pub stake_program: &'me AccountInfo<'info>, + ///System program. The withdraw stake and deposit stake accounts slices follow. + pub system_program: &'me AccountInfo<'info>, +} +#[derive(Copy, Clone, Debug)] +pub struct SlumSwapViaStakeKeys { + ///The authority of src_token_from. Needs to be mutable to support marinade deposit stake. + pub user: Pubkey, + ///The token account to swap src tokens from + pub src_token_from: Pubkey, + ///The token account to receive dest tokens to + pub dest_token_to: Pubkey, + ///The bridge stake account thats withdrawn then deposited. PDA. seeds = ['bridge_stake', user.pubkey, SwapArgs.bridge_stake_seed]. Might be long-lived, make sure the seed is not already in use + pub bridge_stake: Pubkey, + ///The dest_token_mint token account collecting fees. PDA. Seeds = ['fee', dest_token_mint.pubkey] + pub dest_token_fee_token_account: Pubkey, + ///Input token mint. If this is wrapped SOL, the account can be set to read-only + pub src_token_mint: Pubkey, + ///Output token mint. If this is wrapped SOL, the account can be set to read-only + pub dest_token_mint: Pubkey, + ///The slumdog stake account is split from bridge_stake upon stake withdraw and instant unstaked to repay slumlord's flash loan. create_with_seed(bridge_stake.pubkey, 'slumdog', stake_program). Might be long-lived, but should be not in use as long as bridge_stake is not in use + pub slumdog_stake: Pubkey, + ///The slumlord PDA to repay the flash loan to + pub slumlord: Pubkey, + ///The slumlord program ID + pub slumlord_program: Pubkey, + ///instructions sysvar + pub instructions: Pubkey, + ///Sanctum unstake program. unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ + pub unstakeit_program: Pubkey, + ///Sanctum unstake pool. FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd + pub unstake_pool: Pubkey, + ///Sanctum unstake pool SOL reserves. 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ + pub pool_sol_reserves: Pubkey, + ///Sanctum unstake pool Fee account. 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY + pub unstake_fee: Pubkey, + ///Sanctum unstake pool stake account record for slumdog stake. PDA of sanctum unstake program. Seeds = [unstakePool.pubkey, slumdogStake.pubkey]. + pub slumdog_stake_acc_record: Pubkey, + ///Sanctum unstake pool protocol fee account. 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU + pub unstake_protocol_fee: Pubkey, + ///Sanctum unstake pool protocol fee destination. unstakeProtocolFee.destination + pub unstake_protocol_fee_dest: Pubkey, + ///sysvar clock + pub clock: Pubkey, + ///stake program + pub stake_program: Pubkey, + ///System program. The withdraw stake and deposit stake accounts slices follow. + pub system_program: Pubkey, +} +impl From> for SlumSwapViaStakeKeys { + fn from(accounts: SlumSwapViaStakeAccounts) -> Self { + Self { + user: *accounts.user.key, + src_token_from: *accounts.src_token_from.key, + dest_token_to: *accounts.dest_token_to.key, + bridge_stake: *accounts.bridge_stake.key, + dest_token_fee_token_account: *accounts.dest_token_fee_token_account.key, + src_token_mint: *accounts.src_token_mint.key, + dest_token_mint: *accounts.dest_token_mint.key, + slumdog_stake: *accounts.slumdog_stake.key, + slumlord: *accounts.slumlord.key, + slumlord_program: *accounts.slumlord_program.key, + instructions: *accounts.instructions.key, + unstakeit_program: *accounts.unstakeit_program.key, + unstake_pool: *accounts.unstake_pool.key, + pool_sol_reserves: *accounts.pool_sol_reserves.key, + unstake_fee: *accounts.unstake_fee.key, + slumdog_stake_acc_record: *accounts.slumdog_stake_acc_record.key, + unstake_protocol_fee: *accounts.unstake_protocol_fee.key, + unstake_protocol_fee_dest: *accounts.unstake_protocol_fee_dest.key, + clock: *accounts.clock.key, + stake_program: *accounts.stake_program.key, + system_program: *accounts.system_program.key, + } + } +} +impl From for [AccountMeta; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN] { + fn from(keys: SlumSwapViaStakeKeys) -> Self { + [ + AccountMeta { + pubkey: keys.user, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: keys.src_token_from, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.dest_token_to, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.bridge_stake, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.dest_token_fee_token_account, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.src_token_mint, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.dest_token_mint, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.slumdog_stake, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.slumlord, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.slumlord_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.instructions, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.unstakeit_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.unstake_pool, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.pool_sol_reserves, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.unstake_fee, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.slumdog_stake_acc_record, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.unstake_protocol_fee, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.unstake_protocol_fee_dest, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.clock, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.stake_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.system_program, + is_signer: false, + is_writable: false, + }, + ] + } +} +impl From<[Pubkey; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN]> for SlumSwapViaStakeKeys { + fn from(pubkeys: [Pubkey; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN]) -> Self { + Self { + user: pubkeys[0], + src_token_from: pubkeys[1], + dest_token_to: pubkeys[2], + bridge_stake: pubkeys[3], + dest_token_fee_token_account: pubkeys[4], + src_token_mint: pubkeys[5], + dest_token_mint: pubkeys[6], + slumdog_stake: pubkeys[7], + slumlord: pubkeys[8], + slumlord_program: pubkeys[9], + instructions: pubkeys[10], + unstakeit_program: pubkeys[11], + unstake_pool: pubkeys[12], + pool_sol_reserves: pubkeys[13], + unstake_fee: pubkeys[14], + slumdog_stake_acc_record: pubkeys[15], + unstake_protocol_fee: pubkeys[16], + unstake_protocol_fee_dest: pubkeys[17], + clock: pubkeys[18], + stake_program: pubkeys[19], + system_program: pubkeys[20], + } + } +} +impl<'info> From> + for [AccountInfo<'info>; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN] +{ + fn from(accounts: SlumSwapViaStakeAccounts<'_, 'info>) -> Self { + [ + accounts.user.clone(), + accounts.src_token_from.clone(), + accounts.dest_token_to.clone(), + accounts.bridge_stake.clone(), + accounts.dest_token_fee_token_account.clone(), + accounts.src_token_mint.clone(), + accounts.dest_token_mint.clone(), + accounts.slumdog_stake.clone(), + accounts.slumlord.clone(), + accounts.slumlord_program.clone(), + accounts.instructions.clone(), + accounts.unstakeit_program.clone(), + accounts.unstake_pool.clone(), + accounts.pool_sol_reserves.clone(), + accounts.unstake_fee.clone(), + accounts.slumdog_stake_acc_record.clone(), + accounts.unstake_protocol_fee.clone(), + accounts.unstake_protocol_fee_dest.clone(), + accounts.clock.clone(), + accounts.stake_program.clone(), + accounts.system_program.clone(), + ] + } +} +impl<'me, 'info> From<&'me [AccountInfo<'info>; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN]> + for SlumSwapViaStakeAccounts<'me, 'info> +{ + fn from(arr: &'me [AccountInfo<'info>; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN]) -> Self { + Self { + user: &arr[0], + src_token_from: &arr[1], + dest_token_to: &arr[2], + bridge_stake: &arr[3], + dest_token_fee_token_account: &arr[4], + src_token_mint: &arr[5], + dest_token_mint: &arr[6], + slumdog_stake: &arr[7], + slumlord: &arr[8], + slumlord_program: &arr[9], + instructions: &arr[10], + unstakeit_program: &arr[11], + unstake_pool: &arr[12], + pool_sol_reserves: &arr[13], + unstake_fee: &arr[14], + slumdog_stake_acc_record: &arr[15], + unstake_protocol_fee: &arr[16], + unstake_protocol_fee_dest: &arr[17], + clock: &arr[18], + stake_program: &arr[19], + system_program: &arr[20], + } + } +} +pub const SLUM_SWAP_VIA_STAKE_IX_DISCM: u8 = 6u8; +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SlumSwapViaStakeIxArgs { + pub args: SwapViaStakeArgs, +} +#[derive(Clone, Debug, PartialEq)] +pub struct SlumSwapViaStakeIxData(pub SlumSwapViaStakeIxArgs); +impl From for SlumSwapViaStakeIxData { + fn from(args: SlumSwapViaStakeIxArgs) -> Self { + Self(args) + } +} +impl SlumSwapViaStakeIxData { + pub fn deserialize(buf: &[u8]) -> std::io::Result { + let mut reader = buf; + let mut maybe_discm_buf = [0u8; 1]; + reader.read_exact(&mut maybe_discm_buf)?; + let maybe_discm = maybe_discm_buf[0]; + if maybe_discm != SLUM_SWAP_VIA_STAKE_IX_DISCM { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "discm does not match. Expected: {:?}. Received: {:?}", + SLUM_SWAP_VIA_STAKE_IX_DISCM, maybe_discm + ), + )); + } + Ok(Self(SlumSwapViaStakeIxArgs::deserialize(&mut reader)?)) + } + pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&[SLUM_SWAP_VIA_STAKE_IX_DISCM])?; + self.0.serialize(&mut writer) + } + pub fn try_to_vec(&self) -> std::io::Result> { + let mut data = Vec::new(); + self.serialize(&mut data)?; + Ok(data) + } +} +pub fn slum_swap_via_stake_ix, A: Into>( + accounts: K, + args: A, +) -> std::io::Result { + let keys: SlumSwapViaStakeKeys = accounts.into(); + let metas: [AccountMeta; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN] = keys.into(); + let args_full: SlumSwapViaStakeIxArgs = args.into(); + let data: SlumSwapViaStakeIxData = args_full.into(); + Ok(Instruction { + program_id: crate::ID, + accounts: Vec::from(metas), + data: data.try_to_vec()?, + }) +} +pub fn slum_swap_via_stake_invoke<'info, A: Into>( + accounts: SlumSwapViaStakeAccounts<'_, 'info>, + args: A, +) -> ProgramResult { + let ix = slum_swap_via_stake_ix(accounts, args)?; + let account_info: [AccountInfo<'info>; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN] = accounts.into(); + invoke(&ix, &account_info) +} +pub fn slum_swap_via_stake_invoke_signed<'info, A: Into>( + accounts: SlumSwapViaStakeAccounts<'_, 'info>, + args: A, + seeds: &[&[&[u8]]], +) -> ProgramResult { + let ix = slum_swap_via_stake_ix(accounts, args)?; + let account_info: [AccountInfo<'info>; SLUM_SWAP_VIA_STAKE_IX_ACCOUNTS_LEN] = accounts.into(); + invoke_signed(&ix, &account_info, seeds) +} +pub fn slum_swap_via_stake_verify_account_keys( + accounts: SlumSwapViaStakeAccounts<'_, '_>, + keys: SlumSwapViaStakeKeys, +) -> Result<(), (Pubkey, Pubkey)> { + for (actual, expected) in [ + (accounts.user.key, &keys.user), + (accounts.src_token_from.key, &keys.src_token_from), + (accounts.dest_token_to.key, &keys.dest_token_to), + (accounts.bridge_stake.key, &keys.bridge_stake), + ( + accounts.dest_token_fee_token_account.key, + &keys.dest_token_fee_token_account, + ), + (accounts.src_token_mint.key, &keys.src_token_mint), + (accounts.dest_token_mint.key, &keys.dest_token_mint), + (accounts.slumdog_stake.key, &keys.slumdog_stake), + (accounts.slumlord.key, &keys.slumlord), + (accounts.slumlord_program.key, &keys.slumlord_program), + (accounts.instructions.key, &keys.instructions), + (accounts.unstakeit_program.key, &keys.unstakeit_program), + (accounts.unstake_pool.key, &keys.unstake_pool), + (accounts.pool_sol_reserves.key, &keys.pool_sol_reserves), + (accounts.unstake_fee.key, &keys.unstake_fee), + ( + accounts.slumdog_stake_acc_record.key, + &keys.slumdog_stake_acc_record, + ), + ( + accounts.unstake_protocol_fee.key, + &keys.unstake_protocol_fee, + ), + ( + accounts.unstake_protocol_fee_dest.key, + &keys.unstake_protocol_fee_dest, + ), + (accounts.clock.key, &keys.clock), + (accounts.stake_program.key, &keys.stake_program), + (accounts.system_program.key, &keys.system_program), + ] { + if actual != expected { + return Err((*actual, *expected)); + } + } + Ok(()) +} +pub fn slum_swap_via_stake_verify_account_privileges<'me, 'info>( + accounts: SlumSwapViaStakeAccounts<'me, 'info>, +) -> Result<(), (&'me AccountInfo<'info>, ProgramError)> { + for should_be_writable in [ + accounts.user, + accounts.src_token_from, + accounts.dest_token_to, + accounts.bridge_stake, + accounts.dest_token_fee_token_account, + accounts.src_token_mint, + accounts.dest_token_mint, + accounts.slumdog_stake, + accounts.slumlord, + accounts.unstake_pool, + accounts.pool_sol_reserves, + accounts.slumdog_stake_acc_record, + accounts.unstake_protocol_fee_dest, + ] { + if !should_be_writable.is_writable { + return Err((should_be_writable, ProgramError::InvalidAccountData)); + } + } + for should_be_signer in [accounts.user] { + if !should_be_signer.is_signer { + return Err((should_be_signer, ProgramError::MissingRequiredSignature)); + } + } + Ok(()) +} +pub const SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN: usize = 18; +#[derive(Copy, Clone, Debug)] +pub struct SlumWithdrawStakeAccounts<'me, 'info> { + ///The withdraw authority of stake_account. Needs to be mutable and system account to receive slumlord flash loan. + pub user: &'me AccountInfo<'info>, + ///The token account to burn src tokens from in order to withdraw stake + pub src_token_from: &'me AccountInfo<'info>, + ///The bridge stake account thats withdrawn and given to the user. PDA. seeds = ['bridge_stake', user.pubkey, SwapArgs.bridge_stake_seed]. Might be long-lived, make sure the seed is not already in use + pub bridge_stake: &'me AccountInfo<'info>, + ///Input LST token mint + pub src_token_mint: &'me AccountInfo<'info>, + ///The slumdog stake account is split from bridge_stake upon stake withdraw and instant unstaked to repay slumlord's flash loan. create_with_seed(bridge_stake.pubkey, 'slumdog', stake_program). Might be long-lived, but should be not in use as long as bridge_stake is not in use + pub slumdog_stake: &'me AccountInfo<'info>, + ///The slumlord PDA to repay the flash loan to + pub slumlord: &'me AccountInfo<'info>, + ///The slumlord program ID + pub slumlord_program: &'me AccountInfo<'info>, + ///instructions sysvar + pub instructions: &'me AccountInfo<'info>, + ///Sanctum unstake program. unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ + pub unstakeit_program: &'me AccountInfo<'info>, + ///Sanctum unstake pool. FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd + pub unstake_pool: &'me AccountInfo<'info>, + ///Sanctum unstake pool SOL reserves. 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ + pub pool_sol_reserves: &'me AccountInfo<'info>, + ///Sanctum unstake pool Fee account. 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY + pub unstake_fee: &'me AccountInfo<'info>, + ///Sanctum unstake pool stake account record for slumdog stake. PDA of sanctum unstake program. Seeds = [unstakePool.pubkey, slumdogStake.pubkey]. + pub slumdog_stake_acc_record: &'me AccountInfo<'info>, + ///Sanctum unstake pool protocol fee account. 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU + pub unstake_protocol_fee: &'me AccountInfo<'info>, + ///Sanctum unstake pool protocol fee destination. unstakeProtocolFee.destination + pub unstake_protocol_fee_dest: &'me AccountInfo<'info>, + ///sysvar clock + pub clock: &'me AccountInfo<'info>, + ///stake program + pub stake_program: &'me AccountInfo<'info>, + ///System program. The withdraw stake accounts slices follow. + pub system_program: &'me AccountInfo<'info>, +} +#[derive(Copy, Clone, Debug)] +pub struct SlumWithdrawStakeKeys { + ///The withdraw authority of stake_account. Needs to be mutable and system account to receive slumlord flash loan. + pub user: Pubkey, + ///The token account to burn src tokens from in order to withdraw stake + pub src_token_from: Pubkey, + ///The bridge stake account thats withdrawn and given to the user. PDA. seeds = ['bridge_stake', user.pubkey, SwapArgs.bridge_stake_seed]. Might be long-lived, make sure the seed is not already in use + pub bridge_stake: Pubkey, + ///Input LST token mint + pub src_token_mint: Pubkey, + ///The slumdog stake account is split from bridge_stake upon stake withdraw and instant unstaked to repay slumlord's flash loan. create_with_seed(bridge_stake.pubkey, 'slumdog', stake_program). Might be long-lived, but should be not in use as long as bridge_stake is not in use + pub slumdog_stake: Pubkey, + ///The slumlord PDA to repay the flash loan to + pub slumlord: Pubkey, + ///The slumlord program ID + pub slumlord_program: Pubkey, + ///instructions sysvar + pub instructions: Pubkey, + ///Sanctum unstake program. unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ + pub unstakeit_program: Pubkey, + ///Sanctum unstake pool. FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd + pub unstake_pool: Pubkey, + ///Sanctum unstake pool SOL reserves. 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ + pub pool_sol_reserves: Pubkey, + ///Sanctum unstake pool Fee account. 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY + pub unstake_fee: Pubkey, + ///Sanctum unstake pool stake account record for slumdog stake. PDA of sanctum unstake program. Seeds = [unstakePool.pubkey, slumdogStake.pubkey]. + pub slumdog_stake_acc_record: Pubkey, + ///Sanctum unstake pool protocol fee account. 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU + pub unstake_protocol_fee: Pubkey, + ///Sanctum unstake pool protocol fee destination. unstakeProtocolFee.destination + pub unstake_protocol_fee_dest: Pubkey, + ///sysvar clock + pub clock: Pubkey, + ///stake program + pub stake_program: Pubkey, + ///System program. The withdraw stake accounts slices follow. + pub system_program: Pubkey, +} +impl From> for SlumWithdrawStakeKeys { + fn from(accounts: SlumWithdrawStakeAccounts) -> Self { + Self { + user: *accounts.user.key, + src_token_from: *accounts.src_token_from.key, + bridge_stake: *accounts.bridge_stake.key, + src_token_mint: *accounts.src_token_mint.key, + slumdog_stake: *accounts.slumdog_stake.key, + slumlord: *accounts.slumlord.key, + slumlord_program: *accounts.slumlord_program.key, + instructions: *accounts.instructions.key, + unstakeit_program: *accounts.unstakeit_program.key, + unstake_pool: *accounts.unstake_pool.key, + pool_sol_reserves: *accounts.pool_sol_reserves.key, + unstake_fee: *accounts.unstake_fee.key, + slumdog_stake_acc_record: *accounts.slumdog_stake_acc_record.key, + unstake_protocol_fee: *accounts.unstake_protocol_fee.key, + unstake_protocol_fee_dest: *accounts.unstake_protocol_fee_dest.key, + clock: *accounts.clock.key, + stake_program: *accounts.stake_program.key, + system_program: *accounts.system_program.key, + } + } +} +impl From for [AccountMeta; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN] { + fn from(keys: SlumWithdrawStakeKeys) -> Self { + [ + AccountMeta { + pubkey: keys.user, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: keys.src_token_from, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.bridge_stake, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.src_token_mint, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.slumdog_stake, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.slumlord, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.slumlord_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.instructions, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.unstakeit_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.unstake_pool, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.pool_sol_reserves, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.unstake_fee, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.slumdog_stake_acc_record, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.unstake_protocol_fee, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.unstake_protocol_fee_dest, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: keys.clock, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.stake_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.system_program, + is_signer: false, + is_writable: false, + }, + ] + } +} +impl From<[Pubkey; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN]> for SlumWithdrawStakeKeys { + fn from(pubkeys: [Pubkey; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN]) -> Self { + Self { + user: pubkeys[0], + src_token_from: pubkeys[1], + bridge_stake: pubkeys[2], + src_token_mint: pubkeys[3], + slumdog_stake: pubkeys[4], + slumlord: pubkeys[5], + slumlord_program: pubkeys[6], + instructions: pubkeys[7], + unstakeit_program: pubkeys[8], + unstake_pool: pubkeys[9], + pool_sol_reserves: pubkeys[10], + unstake_fee: pubkeys[11], + slumdog_stake_acc_record: pubkeys[12], + unstake_protocol_fee: pubkeys[13], + unstake_protocol_fee_dest: pubkeys[14], + clock: pubkeys[15], + stake_program: pubkeys[16], + system_program: pubkeys[17], + } + } +} +impl<'info> From> + for [AccountInfo<'info>; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN] +{ + fn from(accounts: SlumWithdrawStakeAccounts<'_, 'info>) -> Self { + [ + accounts.user.clone(), + accounts.src_token_from.clone(), + accounts.bridge_stake.clone(), + accounts.src_token_mint.clone(), + accounts.slumdog_stake.clone(), + accounts.slumlord.clone(), + accounts.slumlord_program.clone(), + accounts.instructions.clone(), + accounts.unstakeit_program.clone(), + accounts.unstake_pool.clone(), + accounts.pool_sol_reserves.clone(), + accounts.unstake_fee.clone(), + accounts.slumdog_stake_acc_record.clone(), + accounts.unstake_protocol_fee.clone(), + accounts.unstake_protocol_fee_dest.clone(), + accounts.clock.clone(), + accounts.stake_program.clone(), + accounts.system_program.clone(), + ] + } +} +impl<'me, 'info> From<&'me [AccountInfo<'info>; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN]> + for SlumWithdrawStakeAccounts<'me, 'info> +{ + fn from(arr: &'me [AccountInfo<'info>; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN]) -> Self { + Self { + user: &arr[0], + src_token_from: &arr[1], + bridge_stake: &arr[2], + src_token_mint: &arr[3], + slumdog_stake: &arr[4], + slumlord: &arr[5], + slumlord_program: &arr[6], + instructions: &arr[7], + unstakeit_program: &arr[8], + unstake_pool: &arr[9], + pool_sol_reserves: &arr[10], + unstake_fee: &arr[11], + slumdog_stake_acc_record: &arr[12], + unstake_protocol_fee: &arr[13], + unstake_protocol_fee_dest: &arr[14], + clock: &arr[15], + stake_program: &arr[16], + system_program: &arr[17], + } + } +} +pub const SLUM_WITHDRAW_STAKE_IX_DISCM: u8 = 7u8; +#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SlumWithdrawStakeIxArgs { + pub args: SwapViaStakeArgs, +} +#[derive(Clone, Debug, PartialEq)] +pub struct SlumWithdrawStakeIxData(pub SlumWithdrawStakeIxArgs); +impl From for SlumWithdrawStakeIxData { + fn from(args: SlumWithdrawStakeIxArgs) -> Self { + Self(args) + } +} +impl SlumWithdrawStakeIxData { + pub fn deserialize(buf: &[u8]) -> std::io::Result { + let mut reader = buf; + let mut maybe_discm_buf = [0u8; 1]; + reader.read_exact(&mut maybe_discm_buf)?; + let maybe_discm = maybe_discm_buf[0]; + if maybe_discm != SLUM_WITHDRAW_STAKE_IX_DISCM { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "discm does not match. Expected: {:?}. Received: {:?}", + SLUM_WITHDRAW_STAKE_IX_DISCM, maybe_discm + ), + )); + } + Ok(Self(SlumWithdrawStakeIxArgs::deserialize(&mut reader)?)) + } + pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { + writer.write_all(&[SLUM_WITHDRAW_STAKE_IX_DISCM])?; + self.0.serialize(&mut writer) + } + pub fn try_to_vec(&self) -> std::io::Result> { + let mut data = Vec::new(); + self.serialize(&mut data)?; + Ok(data) + } +} +pub fn slum_withdraw_stake_ix, A: Into>( + accounts: K, + args: A, +) -> std::io::Result { + let keys: SlumWithdrawStakeKeys = accounts.into(); + let metas: [AccountMeta; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN] = keys.into(); + let args_full: SlumWithdrawStakeIxArgs = args.into(); + let data: SlumWithdrawStakeIxData = args_full.into(); + Ok(Instruction { + program_id: crate::ID, + accounts: Vec::from(metas), + data: data.try_to_vec()?, + }) +} +pub fn slum_withdraw_stake_invoke<'info, A: Into>( + accounts: SlumWithdrawStakeAccounts<'_, 'info>, + args: A, +) -> ProgramResult { + let ix = slum_withdraw_stake_ix(accounts, args)?; + let account_info: [AccountInfo<'info>; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN] = accounts.into(); + invoke(&ix, &account_info) +} +pub fn slum_withdraw_stake_invoke_signed<'info, A: Into>( + accounts: SlumWithdrawStakeAccounts<'_, 'info>, + args: A, + seeds: &[&[&[u8]]], +) -> ProgramResult { + let ix = slum_withdraw_stake_ix(accounts, args)?; + let account_info: [AccountInfo<'info>; SLUM_WITHDRAW_STAKE_IX_ACCOUNTS_LEN] = accounts.into(); + invoke_signed(&ix, &account_info, seeds) +} +pub fn slum_withdraw_stake_verify_account_keys( + accounts: SlumWithdrawStakeAccounts<'_, '_>, + keys: SlumWithdrawStakeKeys, +) -> Result<(), (Pubkey, Pubkey)> { + for (actual, expected) in [ + (accounts.user.key, &keys.user), + (accounts.src_token_from.key, &keys.src_token_from), + (accounts.bridge_stake.key, &keys.bridge_stake), + (accounts.src_token_mint.key, &keys.src_token_mint), + (accounts.slumdog_stake.key, &keys.slumdog_stake), + (accounts.slumlord.key, &keys.slumlord), + (accounts.slumlord_program.key, &keys.slumlord_program), + (accounts.instructions.key, &keys.instructions), + (accounts.unstakeit_program.key, &keys.unstakeit_program), + (accounts.unstake_pool.key, &keys.unstake_pool), + (accounts.pool_sol_reserves.key, &keys.pool_sol_reserves), + (accounts.unstake_fee.key, &keys.unstake_fee), + ( + accounts.slumdog_stake_acc_record.key, + &keys.slumdog_stake_acc_record, + ), + ( + accounts.unstake_protocol_fee.key, + &keys.unstake_protocol_fee, + ), + ( + accounts.unstake_protocol_fee_dest.key, + &keys.unstake_protocol_fee_dest, + ), + (accounts.clock.key, &keys.clock), + (accounts.stake_program.key, &keys.stake_program), + (accounts.system_program.key, &keys.system_program), + ] { + if actual != expected { + return Err((*actual, *expected)); + } + } + Ok(()) +} +pub fn slum_withdraw_stake_verify_account_privileges<'me, 'info>( + accounts: SlumWithdrawStakeAccounts<'me, 'info>, +) -> Result<(), (&'me AccountInfo<'info>, ProgramError)> { + for should_be_writable in [ + accounts.user, + accounts.src_token_from, + accounts.bridge_stake, + accounts.src_token_mint, + accounts.slumdog_stake, + accounts.slumlord, + accounts.unstake_pool, + accounts.pool_sol_reserves, + accounts.slumdog_stake_acc_record, + accounts.unstake_protocol_fee_dest, + ] { + if !should_be_writable.is_writable { + return Err((should_be_writable, ProgramError::InvalidAccountData)); + } + } + for should_be_signer in [accounts.user] { + if !should_be_signer.is_signer { + return Err((should_be_signer, ProgramError::MissingRequiredSignature)); + } + } + Ok(()) +} diff --git a/interfaces/stakedex_interface/src/lib.rs b/interfaces/stakedex_interface/src/lib.rs index b8a57d1..eabb9e8 100644 --- a/interfaces/stakedex_interface/src/lib.rs +++ b/interfaces/stakedex_interface/src/lib.rs @@ -1,4 +1,6 @@ solana_program::declare_id!("stkitrT1Uoy18Dk1fTrgPw8W6MVzoCfYoAFT4MLsmhq"); +pub mod typedefs; +pub use typedefs::*; pub mod instructions; pub use instructions::*; pub mod errors; diff --git a/interfaces/stakedex_interface/src/typedefs.rs b/interfaces/stakedex_interface/src/typedefs.rs new file mode 100644 index 0000000..6e17b2b --- /dev/null +++ b/interfaces/stakedex_interface/src/typedefs.rs @@ -0,0 +1,7 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SwapViaStakeArgs { + pub amount: u64, + pub bridge_stake_seed: u32, +} diff --git a/interfaces/stakedex_withdraw_stake_interface/idl.json b/interfaces/stakedex_withdraw_stake_interface/idl.json index 05c46e8..b4508d7 100644 --- a/interfaces/stakedex_withdraw_stake_interface/idl.json +++ b/interfaces/stakedex_withdraw_stake_interface/idl.json @@ -224,6 +224,26 @@ "name": "withdrawStakeDepositAuthority", "isMut": false, "isSigner": false + }, + { + "name": "clock", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "stakeProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false } ], "args": [], diff --git a/interfaces/stakedex_withdraw_stake_interface/src/instructions.rs b/interfaces/stakedex_withdraw_stake_interface/src/instructions.rs index ae68974..de7faee 100644 --- a/interfaces/stakedex_withdraw_stake_interface/src/instructions.rs +++ b/interfaces/stakedex_withdraw_stake_interface/src/instructions.rs @@ -842,7 +842,7 @@ pub fn lido_withdraw_stake_verify_account_privileges<'me, 'info>( } Ok(()) } -pub const MARINADE_WITHDRAW_STAKE_IX_ACCOUNTS_LEN: usize = 8; +pub const MARINADE_WITHDRAW_STAKE_IX_ACCOUNTS_LEN: usize = 12; #[derive(Copy, Clone, Debug)] pub struct MarinadeWithdrawStakeAccounts<'me, 'info> { pub marinade_program: &'me AccountInfo<'info>, @@ -853,6 +853,10 @@ pub struct MarinadeWithdrawStakeAccounts<'me, 'info> { pub withdraw_stake_stake_list: &'me AccountInfo<'info>, pub withdraw_stake_withdraw_authority: &'me AccountInfo<'info>, pub withdraw_stake_deposit_authority: &'me AccountInfo<'info>, + pub clock: &'me AccountInfo<'info>, + pub token_program: &'me AccountInfo<'info>, + pub stake_program: &'me AccountInfo<'info>, + pub system_program: &'me AccountInfo<'info>, } #[derive(Copy, Clone, Debug)] pub struct MarinadeWithdrawStakeKeys { @@ -864,6 +868,10 @@ pub struct MarinadeWithdrawStakeKeys { pub withdraw_stake_stake_list: Pubkey, pub withdraw_stake_withdraw_authority: Pubkey, pub withdraw_stake_deposit_authority: Pubkey, + pub clock: Pubkey, + pub token_program: Pubkey, + pub stake_program: Pubkey, + pub system_program: Pubkey, } impl From> for MarinadeWithdrawStakeKeys { fn from(accounts: MarinadeWithdrawStakeAccounts) -> Self { @@ -876,6 +884,10 @@ impl From> for MarinadeWithdrawStakeKeys { withdraw_stake_stake_list: *accounts.withdraw_stake_stake_list.key, withdraw_stake_withdraw_authority: *accounts.withdraw_stake_withdraw_authority.key, withdraw_stake_deposit_authority: *accounts.withdraw_stake_deposit_authority.key, + clock: *accounts.clock.key, + token_program: *accounts.token_program.key, + stake_program: *accounts.stake_program.key, + system_program: *accounts.system_program.key, } } } @@ -922,6 +934,26 @@ impl From for [AccountMeta; MARINADE_WITHDRAW_STAKE_I is_signer: false, is_writable: false, }, + AccountMeta { + pubkey: keys.clock, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.token_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.stake_program, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: keys.system_program, + is_signer: false, + is_writable: false, + }, ] } } @@ -936,6 +968,10 @@ impl From<[Pubkey; MARINADE_WITHDRAW_STAKE_IX_ACCOUNTS_LEN]> for MarinadeWithdra withdraw_stake_stake_list: pubkeys[5], withdraw_stake_withdraw_authority: pubkeys[6], withdraw_stake_deposit_authority: pubkeys[7], + clock: pubkeys[8], + token_program: pubkeys[9], + stake_program: pubkeys[10], + system_program: pubkeys[11], } } } @@ -952,6 +988,10 @@ impl<'info> From> accounts.withdraw_stake_stake_list.clone(), accounts.withdraw_stake_withdraw_authority.clone(), accounts.withdraw_stake_deposit_authority.clone(), + accounts.clock.clone(), + accounts.token_program.clone(), + accounts.stake_program.clone(), + accounts.system_program.clone(), ] } } @@ -968,6 +1008,10 @@ impl<'me, 'info> From<&'me [AccountInfo<'info>; MARINADE_WITHDRAW_STAKE_IX_ACCOU withdraw_stake_stake_list: &arr[5], withdraw_stake_withdraw_authority: &arr[6], withdraw_stake_deposit_authority: &arr[7], + clock: &arr[8], + token_program: &arr[9], + stake_program: &arr[10], + system_program: &arr[11], } } } @@ -1062,6 +1106,10 @@ pub fn marinade_withdraw_stake_verify_account_keys( accounts.withdraw_stake_deposit_authority.key, &keys.withdraw_stake_deposit_authority, ), + (accounts.clock.key, &keys.clock), + (accounts.token_program.key, &keys.token_program), + (accounts.stake_program.key, &keys.stake_program), + (accounts.system_program.key, &keys.system_program), ] { if actual != expected { return Err((*actual, *expected)); diff --git a/libs/lido/Cargo.toml b/libs/lido/Cargo.toml index d40343d..c2ef9fb 100644 --- a/libs/lido/Cargo.toml +++ b/libs/lido/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" anyhow = { workspace = true } bincode = { workspace = true } borsh = { workspace = true } -lido = { git = "https://github.com/igneous-labs/solido", branch = "mod/2.0.0-loose-deps", features = ["no-entrypoint"] } +lido = { workspace = true } solana-program = { workspace = true } spl-token = { workspace = true } stakedex_deposit_sol_interface = { workspace = true } diff --git a/libs/marinade/Cargo.toml b/libs/marinade/Cargo.toml index d30560c..df363d7 100644 --- a/libs/marinade/Cargo.toml +++ b/libs/marinade/Cargo.toml @@ -8,9 +8,11 @@ edition = "2021" [dependencies] anyhow = { workspace = true } borsh = { workspace = true } -marinade_finance_interface = { git = "https://github.com/igneous-labs/marinade_finance_interface", rev = "d625979" } +marinade_finance_interface = { workspace = true } solana-program = { workspace = true } +solana-sdk = { workspace = true } spl-token = { workspace = true } stakedex_deposit_sol_interface = { workspace = true } stakedex_deposit_stake_interface = { workspace = true } +stakedex_withdraw_stake_interface = { workspace = true } stakedex_sdk_common = { workspace = true } diff --git a/libs/marinade/src/consts.rs b/libs/marinade/src/consts.rs index bceffb9..1566d15 100644 --- a/libs/marinade/src/consts.rs +++ b/libs/marinade/src/consts.rs @@ -1,3 +1,5 @@ // struct ValidatorRecord is 53 bytes long borsh serialized -// but marinade serializes it with 8-bytes padding so it's 61 bytes in accountinfo.data +// + 8-byte discriminant marinade serializes it with pub const VALIDATOR_RECORD_BYTE_LENGTH: usize = 61; + +pub const STAKE_RECORD_BYTE_LENGTH: usize = 56; diff --git a/libs/marinade/src/lib.rs b/libs/marinade/src/lib.rs index 9f3ec32..efa6cae 100644 --- a/libs/marinade/src/lib.rs +++ b/libs/marinade/src/lib.rs @@ -1,18 +1,19 @@ mod calc; mod consts; +mod stake_system; mod stakedex_traits; mod state; mod validator_system; use anyhow::{anyhow, Result}; -use borsh::BorshDeserialize; -use consts::VALIDATOR_RECORD_BYTE_LENGTH; +use consts::{STAKE_RECORD_BYTE_LENGTH, VALIDATOR_RECORD_BYTE_LENGTH}; use marinade_finance_interface::{ - Fee, LiqPool, List, StakeSystem, State, ValidatorRecord, ValidatorSystem, + Fee, FeeCents, LiqPool, List, StakeRecord, StakeSystem, State, ValidatorRecord, ValidatorSystem, }; use solana_program::{borsh::try_from_slice_unchecked, pubkey::Pubkey}; pub use stakedex_traits::*; +use state::StateWrapper; pub const MARINADE_LABEL: &str = "Marinade"; @@ -20,6 +21,8 @@ pub const MARINADE_LABEL: &str = "Marinade"; pub struct MarinadeStakedex { state: State, validator_records: Vec, + stake_records: Vec, + // stake_accounts: Vec } impl Default for MarinadeStakedex { @@ -28,10 +31,11 @@ impl Default for MarinadeStakedex { account: Pubkey::default(), item_size: 0, count: 0, - new_account: Pubkey::default(), - copied_count: 0, + reserved1: Pubkey::default(), + reserved2: 0, }; let zero_fee = Fee { basis_points: 0 }; + let zero_fee_cents = FeeCents { bp_cents: 0 }; Self { state: State { msol_mint: Pubkey::default(), @@ -68,7 +72,7 @@ impl Default for MarinadeStakedex { lp_liquidity_target: 0, lp_max_fee: zero_fee.clone(), lp_min_fee: zero_fee.clone(), - treasury_cut: zero_fee, + treasury_cut: zero_fee.clone(), lp_supply: 0, lent_from_sol_leg: 0, liquidity_sol_cap: 0, @@ -83,8 +87,17 @@ impl Default for MarinadeStakedex { min_withdraw: 0, staking_sol_cap: 0, emergency_cooling_down: 0, + pause_authority: Pubkey::default(), + paused: false, + delayed_unstake_fee: zero_fee_cents.clone(), + withdraw_stake_account_fee: zero_fee_cents, + withdraw_stake_account_enabled: false, + last_stake_move_epoch: 0, + stake_moved: 0, + max_stake_moved_per_epoch: zero_fee, }, validator_records: Vec::new(), + stake_records: Vec::new(), } } } @@ -97,13 +110,12 @@ impl MarinadeStakedex { Ok(()) } - /// data is account data of state.validator_system.validator_list.account + /// data is account data of state.validator_system.validator_list.account. + /// Must be called after [`Self::update_state`] to ensure len is latest pub fn update_validator_records(&mut self, data: &[u8]) -> Result<()> { - // first 8 bytes are len - let len_slice = data - .get(..8) - .ok_or_else(|| anyhow!("Could not read validator records len"))?; - let len = u64::try_from_slice(len_slice)?; + let len: usize = StateWrapper(&self.state) + .validator_list_count() + .try_into()?; let records_slice = data .get(8..) .ok_or_else(|| anyhow!("Could not read validator records data"))?; @@ -112,7 +124,7 @@ impl MarinadeStakedex { .enumerate(); self.validator_records.clear(); for (index, record) in validator_record_iter { - if len == index as u64 { + if len == index { break; } self.validator_records @@ -120,4 +132,25 @@ impl MarinadeStakedex { } Ok(()) } + + /// data is account data of state.stake_system.stake_list.account. + /// Must be called after [`Self::update_state`] to ensure len is latest + pub fn update_stake_records(&mut self, data: &[u8]) -> Result<()> { + let len: usize = StateWrapper(&self.state).stake_list_count().try_into()?; + let records_slice = data + .get(8..) + .ok_or_else(|| anyhow!("Could not read stake records data"))?; + let stake_record_iter = records_slice + .chunks_exact(STAKE_RECORD_BYTE_LENGTH) + .enumerate(); + self.stake_records.clear(); + for (index, record) in stake_record_iter { + if len == index { + break; + } + self.stake_records + .push(try_from_slice_unchecked::(record)?); + } + Ok(()) + } } diff --git a/libs/marinade/src/stake_system.rs b/libs/marinade/src/stake_system.rs new file mode 100644 index 0000000..7570bd1 --- /dev/null +++ b/libs/marinade/src/stake_system.rs @@ -0,0 +1,47 @@ +use anyhow::anyhow; +use borsh::BorshDeserialize; +use solana_program::{pubkey::Pubkey, stake::state::StakeState}; +use solana_sdk::account::Account; + +/// Simplified version of solana_program::StakeState +/// containing only the params we require to give a quote +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct WithdrawableStakeAcc { + pub lamports: u64, + pub voter: Pubkey, + + /// state.delegation.stake + pub stake: u64, +} + +impl TryFrom<&Account> for WithdrawableStakeAcc { + type Error = anyhow::Error; + + /// Errors if: + /// - account is not a stake account + /// - stake account is not withdrawable + /// - stake account is not delegated + /// - deactivation_epoch is not u64::MAX + fn try_from(Account { lamports, data, .. }: &Account) -> Result { + let state = StakeState::try_from_slice(data)?; + let delegation = state + .delegation() + .ok_or_else(|| anyhow!("stake not delegated"))?; + if delegation.deactivation_epoch != u64::MAX { + return Err(anyhow!("stake not active")); + } + Ok(Self { + lamports: *lamports, + voter: delegation.voter_pubkey, + stake: delegation.stake, + }) + } +} + +impl TryFrom for WithdrawableStakeAcc { + type Error = anyhow::Error; + + fn try_from(value: Account) -> Result { + Self::try_from(&value) + } +} diff --git a/libs/marinade/src/stakedex_traits/base.rs b/libs/marinade/src/stakedex_traits/base.rs index 1688430..cd3977b 100644 --- a/libs/marinade/src/stakedex_traits/base.rs +++ b/libs/marinade/src/stakedex_traits/base.rs @@ -36,6 +36,7 @@ impl BaseStakePoolAmm for MarinadeStakedex { vec![ marinade_state::ID, self.state.validator_system.validator_list.account, + self.state.stake_system.stake_list.account, ] } @@ -46,14 +47,23 @@ impl BaseStakePoolAmm for MarinadeStakedex { .data .as_ref(); self.update_state(state_data)?; + + let validator_list_pubkey = self.state.validator_system.validator_list.account; let validator_records_data = accounts_map - .get(&self.state.validator_system.validator_list.account) - .ok_or_else(|| { - account_missing_err(&self.state.validator_system.validator_list.account) - })? + .get(&validator_list_pubkey) + .ok_or_else(|| account_missing_err(&validator_list_pubkey))? .data .as_ref(); self.update_validator_records(validator_records_data)?; + + let stake_list_pubkey = self.state.stake_system.stake_list.account; + let stake_records_data = accounts_map + .get(&stake_list_pubkey) + .ok_or_else(|| account_missing_err(&stake_list_pubkey))? + .data + .as_ref(); + self.update_stake_records(stake_records_data)?; + Ok(()) } } diff --git a/libs/marinade/src/stakedex_traits/deposit_sol.rs b/libs/marinade/src/stakedex_traits/deposit_sol.rs index 225fc6b..926837c 100644 --- a/libs/marinade/src/stakedex_traits/deposit_sol.rs +++ b/libs/marinade/src/stakedex_traits/deposit_sol.rs @@ -7,7 +7,7 @@ use crate::{state::StateWrapper, MarinadeStakedex}; impl DepositSol for MarinadeStakedex { fn can_accept_sol_deposits(&self) -> bool { - true + !self.state.paused } fn get_deposit_sol_quote_unchecked(&self, user_lamports: u64) -> Result { diff --git a/libs/marinade/src/stakedex_traits/deposit_stake.rs b/libs/marinade/src/stakedex_traits/deposit_stake.rs index e3bc80e..246162d 100644 --- a/libs/marinade/src/stakedex_traits/deposit_stake.rs +++ b/libs/marinade/src/stakedex_traits/deposit_stake.rs @@ -10,7 +10,7 @@ use crate::{state::StateWrapper, validator_system::ValidatorRecordWrapper, Marin impl DepositStake for MarinadeStakedex { fn can_accept_stake_deposits(&self) -> bool { - true + !self.state.paused } fn get_deposit_stake_quote_unchecked( diff --git a/libs/marinade/src/stakedex_traits/mod.rs b/libs/marinade/src/stakedex_traits/mod.rs index 4114fae..1b6161e 100644 --- a/libs/marinade/src/stakedex_traits/mod.rs +++ b/libs/marinade/src/stakedex_traits/mod.rs @@ -1,7 +1,9 @@ mod base; mod deposit_sol; mod deposit_stake; +mod withdraw_stake; pub use base::*; pub use deposit_sol::*; pub use deposit_stake::*; +pub use withdraw_stake::*; diff --git a/libs/marinade/src/stakedex_traits/withdraw_stake.rs b/libs/marinade/src/stakedex_traits/withdraw_stake.rs new file mode 100644 index 0000000..28577cc --- /dev/null +++ b/libs/marinade/src/stakedex_traits/withdraw_stake.rs @@ -0,0 +1,42 @@ +use anyhow::Result; +use solana_program::{instruction::Instruction, stake, system_program, sysvar}; +use stakedex_sdk_common::{ + marinade_program, marinade_state, WithdrawStake, WithdrawStakeBase, WithdrawStakeQuote, +}; +use stakedex_withdraw_stake_interface::{marinade_withdraw_stake_ix, MarinadeWithdrawStakeKeys}; + +use crate::MarinadeStakedex; + +impl WithdrawStakeBase for MarinadeStakedex { + fn can_accept_stake_withdrawals(&self) -> bool { + !self.state.paused && self.state.withdraw_stake_account_enabled + } + + fn virtual_ix(&self, _quote: &WithdrawStakeQuote) -> Result { + Ok(marinade_withdraw_stake_ix(MarinadeWithdrawStakeKeys { + marinade_program: marinade_program::ID, + withdraw_stake_marinade_state: marinade_state::ID, + withdraw_stake_marinade_treasury: self.state.treasury_msol_account, + withdraw_stake_validator_list: self.state.validator_system.validator_list.account, + withdraw_stake_stake_list: self.state.stake_system.stake_list.account, + withdraw_stake_withdraw_authority: marinade_program::STAKE_WITHDRAW_AUTH_ID, + withdraw_stake_deposit_authority: marinade_program::STAKE_DEPOSIT_AUTH_ID, + clock: sysvar::clock::ID, + token_program: spl_token::ID, + stake_program: stake::program::ID, + system_program: system_program::ID, + // TODO: marinade doesn't provide an easy way of matching voter to stake account + // so we might have to fetch all their stake accounts on update... + withdraw_stake_stake_to_split: Default::default(), + })?) + } +} + +impl WithdrawStake for MarinadeStakedex { + fn withdraw_stake_quote_iter_dyn( + &self, + _withdraw_amount: u64, + ) -> Box + '_> { + todo!() + } +} diff --git a/libs/marinade/src/state.rs b/libs/marinade/src/state.rs index 7d266dd..81bdaee 100644 --- a/libs/marinade/src/state.rs +++ b/libs/marinade/src/state.rs @@ -53,4 +53,12 @@ impl<'a> StateWrapper<'a> { } Ok(()) } + + pub fn validator_list_count(&self) -> u32 { + self.0.validator_system.validator_list.count + } + + pub fn stake_list_count(&self) -> u32 { + self.0.stake_system.stake_list.count + } } diff --git a/stakedex_sdk/src/lib.rs b/stakedex_sdk/src/lib.rs index be05803..5293bb4 100644 --- a/stakedex_sdk/src/lib.rs +++ b/stakedex_sdk/src/lib.rs @@ -9,8 +9,8 @@ use solana_sdk::{ use spl_token::native_mint; pub use stakedex_interface::ID as stakedex_program_id; use stakedex_interface::{ - DepositStakeKeys, StakeWrappedSolIxArgs, StakeWrappedSolKeys, SwapViaStakeIxArgs, - SwapViaStakeKeys, + DepositStakeKeys, StakeWrappedSolIxArgs, StakeWrappedSolKeys, SwapViaStakeArgs, + SwapViaStakeIxArgs, SwapViaStakeKeys, }; use stakedex_lido::LidoStakedex; use stakedex_marinade::MarinadeStakedex; @@ -367,8 +367,10 @@ impl Stakedex { bridge_stake, }, SwapViaStakeIxArgs { - amount: swap_params.in_amount, - bridge_stake_seed, + args: SwapViaStakeArgs { + amount: swap_params.in_amount, + bridge_stake_seed, + }, }, )?; for mint_idx in [