Skip to content

Commit 2ac2cf7

Browse files
authored
L-03: Slippage protection (#58)
* fix: slippage * fix: conflicts * fix: conflicts
1 parent 0f1d277 commit 2ac2cf7

10 files changed

Lines changed: 163 additions & 35 deletions

File tree

cli/src/interceptor.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ pub enum StakeDepositInterceptorActions {
176176

177177
/// Amount of pool tokens to withdraw
178178
#[arg(long)]
179-
amount: u64,
179+
pool_tokens_in: u64,
180+
181+
/// Minimum amount of lamports to receive
182+
#[arg(long)]
183+
minimum_lamports_out: Option<u64>,
180184
},
181185

182186
/// Fund hopper

cli/src/interceptor_handler.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ impl StakeDepositInterceptorCliHandler {
333333
user_stake_authority,
334334
fee_rebate_recipient,
335335
spl_stake_pool_program_id,
336-
amount,
336+
pool_tokens_in,
337+
minimum_lamports_out,
337338
},
338339
} => {
339340
self.withdraw_stake_whitelisted(
@@ -344,7 +345,8 @@ impl StakeDepositInterceptorCliHandler {
344345
user_stake_authority,
345346
fee_rebate_recipient,
346347
spl_stake_pool_program_id,
347-
amount,
348+
pool_tokens_in,
349+
minimum_lamports_out,
348350
)
349351
.await
350352
}
@@ -926,7 +928,8 @@ impl StakeDepositInterceptorCliHandler {
926928
user_stake_authority: Pubkey,
927929
fee_rebate_recipient: Pubkey,
928930
spl_stake_pool_program_id: Pubkey,
929-
amount: u64,
931+
pool_tokens_in: u64,
932+
minimum_lamports_out: Option<u64>,
930933
) -> anyhow::Result<()> {
931934
let rpc_client = self.get_rpc_client();
932935

@@ -985,7 +988,12 @@ impl StakeDepositInterceptorCliHandler {
985988
.clock(solana_clock::Clock::id())
986989
.stake_program(solana_stake_interface::program::id())
987990
.spl_stake_pool_program(spl_stake_pool_program_id)
988-
.amount(amount);
991+
.pool_tokens_in(pool_tokens_in);
992+
993+
if let Some(lamports_out) = minimum_lamports_out {
994+
ix_builder.minimum_lamports_out(lamports_out);
995+
}
996+
989997
let mut ix = ix_builder.instruction();
990998
ix.program_id = self.stake_deposit_interceptor_program_id;
991999

clients/rust/stake-deposit-interceptor/src/generated/instructions/deposit_stake_whitelisted.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,17 @@ pub struct DepositStakeWhitelisted {
5454
}
5555

5656
impl DepositStakeWhitelisted {
57-
pub fn instruction(&self) -> solana_instruction::Instruction {
58-
self.instruction_with_remaining_accounts(&[])
57+
pub fn instruction(
58+
&self,
59+
args: DepositStakeWhitelistedInstructionArgs,
60+
) -> solana_instruction::Instruction {
61+
self.instruction_with_remaining_accounts(args, &[])
5962
}
6063
#[allow(clippy::arithmetic_side_effects)]
6164
#[allow(clippy::vec_init_then_push)]
6265
pub fn instruction_with_remaining_accounts(
6366
&self,
67+
args: DepositStakeWhitelistedInstructionArgs,
6468
remaining_accounts: &[solana_instruction::AccountMeta],
6569
) -> solana_instruction::Instruction {
6670
let mut accounts = Vec::with_capacity(19 + remaining_accounts.len());
@@ -134,9 +138,11 @@ impl DepositStakeWhitelisted {
134138
false,
135139
));
136140
accounts.extend_from_slice(remaining_accounts);
137-
let data = DepositStakeWhitelistedInstructionData::new()
141+
let mut data = DepositStakeWhitelistedInstructionData::new()
138142
.try_to_vec()
139143
.unwrap();
144+
let mut args = args.try_to_vec().unwrap();
145+
data.append(&mut args);
140146

141147
solana_instruction::Instruction {
142148
program_id: crate::STAKE_DEPOSIT_INTERCEPTOR_ID,
@@ -168,6 +174,18 @@ impl Default for DepositStakeWhitelistedInstructionData {
168174
}
169175
}
170176

177+
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]
178+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179+
pub struct DepositStakeWhitelistedInstructionArgs {
180+
pub minimum_pool_tokens_out: Option<u64>,
181+
}
182+
183+
impl DepositStakeWhitelistedInstructionArgs {
184+
pub(crate) fn try_to_vec(&self) -> Result<Vec<u8>, std::io::Error> {
185+
borsh::to_vec(self)
186+
}
187+
}
188+
171189
/// Instruction builder for `DepositStakeWhitelisted`.
172190
///
173191
/// ### Accounts:
@@ -212,6 +230,7 @@ pub struct DepositStakeWhitelistedBuilder {
212230
stake_program: Option<solana_pubkey::Pubkey>,
213231
spl_stake_pool_program: Option<solana_pubkey::Pubkey>,
214232
system_program: Option<solana_pubkey::Pubkey>,
233+
minimum_pool_tokens_out: Option<u64>,
215234
__remaining_accounts: Vec<solana_instruction::AccountMeta>,
216235
}
217236

@@ -344,6 +363,12 @@ impl DepositStakeWhitelistedBuilder {
344363
self.system_program = Some(system_program);
345364
self
346365
}
366+
/// `[optional argument]`
367+
#[inline(always)]
368+
pub fn minimum_pool_tokens_out(&mut self, minimum_pool_tokens_out: u64) -> &mut Self {
369+
self.minimum_pool_tokens_out = Some(minimum_pool_tokens_out);
370+
self
371+
}
347372
/// Add an additional account to the instruction.
348373
#[inline(always)]
349374
pub fn add_remaining_account(&mut self, account: solana_instruction::AccountMeta) -> &mut Self {
@@ -398,8 +423,11 @@ impl DepositStakeWhitelistedBuilder {
398423
.system_program
399424
.unwrap_or(solana_pubkey::pubkey!("11111111111111111111111111111111")),
400425
};
426+
let args = DepositStakeWhitelistedInstructionArgs {
427+
minimum_pool_tokens_out: self.minimum_pool_tokens_out.clone(),
428+
};
401429

402-
accounts.instruction_with_remaining_accounts(&self.__remaining_accounts)
430+
accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts)
403431
}
404432
}
405433

@@ -487,12 +515,15 @@ pub struct DepositStakeWhitelistedCpi<'a, 'b> {
487515
pub spl_stake_pool_program: &'b solana_account_info::AccountInfo<'a>,
488516
/// System program
489517
pub system_program: &'b solana_account_info::AccountInfo<'a>,
518+
/// The arguments for the instruction.
519+
pub __args: DepositStakeWhitelistedInstructionArgs,
490520
}
491521

492522
impl<'a, 'b> DepositStakeWhitelistedCpi<'a, 'b> {
493523
pub fn new(
494524
program: &'b solana_account_info::AccountInfo<'a>,
495525
accounts: DepositStakeWhitelistedCpiAccounts<'a, 'b>,
526+
args: DepositStakeWhitelistedInstructionArgs,
496527
) -> Self {
497528
Self {
498529
__program: program,
@@ -515,6 +546,7 @@ impl<'a, 'b> DepositStakeWhitelistedCpi<'a, 'b> {
515546
stake_program: accounts.stake_program,
516547
spl_stake_pool_program: accounts.spl_stake_pool_program,
517548
system_program: accounts.system_program,
549+
__args: args,
518550
}
519551
}
520552
#[inline(always)]
@@ -624,9 +656,11 @@ impl<'a, 'b> DepositStakeWhitelistedCpi<'a, 'b> {
624656
is_writable: remaining_account.2,
625657
})
626658
});
627-
let data = DepositStakeWhitelistedInstructionData::new()
659+
let mut data = DepositStakeWhitelistedInstructionData::new()
628660
.try_to_vec()
629661
.unwrap();
662+
let mut args = self.__args.try_to_vec().unwrap();
663+
data.append(&mut args);
630664

631665
let instruction = solana_instruction::Instruction {
632666
program_id: crate::STAKE_DEPOSIT_INTERCEPTOR_ID,
@@ -717,6 +751,7 @@ impl<'a, 'b> DepositStakeWhitelistedCpiBuilder<'a, 'b> {
717751
stake_program: None,
718752
spl_stake_pool_program: None,
719753
system_program: None,
754+
minimum_pool_tokens_out: None,
720755
__remaining_accounts: Vec::new(),
721756
});
722757
Self { instruction }
@@ -883,6 +918,12 @@ impl<'a, 'b> DepositStakeWhitelistedCpiBuilder<'a, 'b> {
883918
self.instruction.system_program = Some(system_program);
884919
self
885920
}
921+
/// `[optional argument]`
922+
#[inline(always)]
923+
pub fn minimum_pool_tokens_out(&mut self, minimum_pool_tokens_out: u64) -> &mut Self {
924+
self.instruction.minimum_pool_tokens_out = Some(minimum_pool_tokens_out);
925+
self
926+
}
886927
/// Add an additional account to the instruction.
887928
#[inline(always)]
888929
pub fn add_remaining_account(
@@ -917,6 +958,9 @@ impl<'a, 'b> DepositStakeWhitelistedCpiBuilder<'a, 'b> {
917958
#[allow(clippy::clone_on_copy)]
918959
#[allow(clippy::vec_init_then_push)]
919960
pub fn invoke_signed(&self, signers_seeds: &[&[&[u8]]]) -> solana_program_error::ProgramResult {
961+
let args = DepositStakeWhitelistedInstructionArgs {
962+
minimum_pool_tokens_out: self.instruction.minimum_pool_tokens_out.clone(),
963+
};
920964
let instruction = DepositStakeWhitelistedCpi {
921965
__program: self.instruction.__program,
922966

@@ -1002,6 +1046,7 @@ impl<'a, 'b> DepositStakeWhitelistedCpiBuilder<'a, 'b> {
10021046
.instruction
10031047
.system_program
10041048
.expect("system_program is not set"),
1049+
__args: args,
10051050
};
10061051
instruction.invoke_signed_with_remaining_accounts(
10071052
signers_seeds,
@@ -1032,6 +1077,7 @@ struct DepositStakeWhitelistedCpiBuilderInstruction<'a, 'b> {
10321077
stake_program: Option<&'b solana_account_info::AccountInfo<'a>>,
10331078
spl_stake_pool_program: Option<&'b solana_account_info::AccountInfo<'a>>,
10341079
system_program: Option<&'b solana_account_info::AccountInfo<'a>>,
1080+
minimum_pool_tokens_out: Option<u64>,
10351081
/// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`.
10361082
__remaining_accounts: Vec<(&'b solana_account_info::AccountInfo<'a>, bool, bool)>,
10371083
}

clients/rust/stake-deposit-interceptor/src/generated/instructions/withdraw_stake_whitelisted.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ impl Default for WithdrawStakeWhitelistedInstructionData {
183183
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]
184184
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185185
pub struct WithdrawStakeWhitelistedInstructionArgs {
186-
pub amount: u64,
186+
pub pool_tokens_in: u64,
187+
pub minimum_lamports_out: u64,
187188
}
188189

189190
impl WithdrawStakeWhitelistedInstructionArgs {
@@ -238,7 +239,8 @@ pub struct WithdrawStakeWhitelistedBuilder {
238239
stake_program: Option<solana_pubkey::Pubkey>,
239240
spl_stake_pool_program: Option<solana_pubkey::Pubkey>,
240241
system_program: Option<solana_pubkey::Pubkey>,
241-
amount: Option<u64>,
242+
pool_tokens_in: Option<u64>,
243+
minimum_lamports_out: Option<u64>,
242244
__remaining_accounts: Vec<solana_instruction::AccountMeta>,
243245
}
244246

@@ -387,8 +389,13 @@ impl WithdrawStakeWhitelistedBuilder {
387389
self
388390
}
389391
#[inline(always)]
390-
pub fn amount(&mut self, amount: u64) -> &mut Self {
391-
self.amount = Some(amount);
392+
pub fn pool_tokens_in(&mut self, pool_tokens_in: u64) -> &mut Self {
393+
self.pool_tokens_in = Some(pool_tokens_in);
394+
self
395+
}
396+
#[inline(always)]
397+
pub fn minimum_lamports_out(&mut self, minimum_lamports_out: u64) -> &mut Self {
398+
self.minimum_lamports_out = Some(minimum_lamports_out);
392399
self
393400
}
394401
/// Add an additional account to the instruction.
@@ -455,7 +462,14 @@ impl WithdrawStakeWhitelistedBuilder {
455462
.unwrap_or(solana_pubkey::pubkey!("11111111111111111111111111111111")),
456463
};
457464
let args = WithdrawStakeWhitelistedInstructionArgs {
458-
amount: self.amount.clone().expect("amount is not set"),
465+
pool_tokens_in: self
466+
.pool_tokens_in
467+
.clone()
468+
.expect("pool_tokens_in is not set"),
469+
minimum_lamports_out: self
470+
.minimum_lamports_out
471+
.clone()
472+
.expect("minimum_lamports_out is not set"),
459473
};
460474

461475
accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts)
@@ -794,7 +808,8 @@ impl<'a, 'b> WithdrawStakeWhitelistedCpiBuilder<'a, 'b> {
794808
stake_program: None,
795809
spl_stake_pool_program: None,
796810
system_program: None,
797-
amount: None,
811+
pool_tokens_in: None,
812+
minimum_lamports_out: None,
798813
__remaining_accounts: Vec::new(),
799814
});
800815
Self { instruction }
@@ -971,8 +986,13 @@ impl<'a, 'b> WithdrawStakeWhitelistedCpiBuilder<'a, 'b> {
971986
self
972987
}
973988
#[inline(always)]
974-
pub fn amount(&mut self, amount: u64) -> &mut Self {
975-
self.instruction.amount = Some(amount);
989+
pub fn pool_tokens_in(&mut self, pool_tokens_in: u64) -> &mut Self {
990+
self.instruction.pool_tokens_in = Some(pool_tokens_in);
991+
self
992+
}
993+
#[inline(always)]
994+
pub fn minimum_lamports_out(&mut self, minimum_lamports_out: u64) -> &mut Self {
995+
self.instruction.minimum_lamports_out = Some(minimum_lamports_out);
976996
self
977997
}
978998
/// Add an additional account to the instruction.
@@ -1010,7 +1030,16 @@ impl<'a, 'b> WithdrawStakeWhitelistedCpiBuilder<'a, 'b> {
10101030
#[allow(clippy::vec_init_then_push)]
10111031
pub fn invoke_signed(&self, signers_seeds: &[&[&[u8]]]) -> solana_program_error::ProgramResult {
10121032
let args = WithdrawStakeWhitelistedInstructionArgs {
1013-
amount: self.instruction.amount.clone().expect("amount is not set"),
1033+
pool_tokens_in: self
1034+
.instruction
1035+
.pool_tokens_in
1036+
.clone()
1037+
.expect("pool_tokens_in is not set"),
1038+
minimum_lamports_out: self
1039+
.instruction
1040+
.minimum_lamports_out
1041+
.clone()
1042+
.expect("minimum_lamports_out is not set"),
10141043
};
10151044
let instruction = WithdrawStakeWhitelistedCpi {
10161045
__program: self.instruction.__program,
@@ -1134,7 +1163,8 @@ struct WithdrawStakeWhitelistedCpiBuilderInstruction<'a, 'b> {
11341163
stake_program: Option<&'b solana_account_info::AccountInfo<'a>>,
11351164
spl_stake_pool_program: Option<&'b solana_account_info::AccountInfo<'a>>,
11361165
system_program: Option<&'b solana_account_info::AccountInfo<'a>>,
1137-
amount: Option<u64>,
1166+
pool_tokens_in: Option<u64>,
1167+
minimum_lamports_out: Option<u64>,
11381168
/// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`.
11391169
__remaining_accounts: Vec<(&'b solana_account_info::AccountInfo<'a>, bool, bool)>,
11401170
}

stake_deposit_interceptor/idl/stake_deposit_interceptor.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,14 @@
772772
]
773773
}
774774
],
775-
"args": [],
775+
"args": [
776+
{
777+
"name": "minimumPoolTokensOut",
778+
"type": {
779+
"option": "u64"
780+
}
781+
}
782+
],
776783
"discriminant": {
777784
"type": "u8",
778785
"value": 6
@@ -944,7 +951,11 @@
944951
],
945952
"args": [
946953
{
947-
"name": "amount",
954+
"name": "poolTokensIn",
955+
"type": "u64"
956+
},
957+
{
958+
"name": "minimumLamportsOut",
948959
"type": "u64"
949960
}
950961
],

stake_deposit_interceptor/src/instruction.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,9 @@ pub enum StakeDepositInterceptorInstruction {
412412
#[account(16, name = "stake_program", desc = "Stake program id")]
413413
#[account(17, name = "spl_stake_pool_program", desc = "SPL Stake Pool Program")]
414414
#[account(18, name = "system_program", desc = "System program")]
415-
DepositStakeWhitelisted,
415+
DepositStakeWhitelisted {
416+
minimum_pool_tokens_out: Option<u64>,
417+
},
416418

417419
/// Wraps spl-stake-pool WithdrawStake with whitelist verification.
418420
///
@@ -501,7 +503,10 @@ pub enum StakeDepositInterceptorInstruction {
501503
#[account(17, name = "stake_program", desc = "Stake program id")]
502504
#[account(18, name = "spl_stake_pool_program", desc = "SPL Stake Pool Program")]
503505
#[account(19, name = "system_program", desc = "System program")]
504-
WithdrawStakeWhitelisted { amount: u64 },
506+
WithdrawStakeWhitelisted {
507+
pool_tokens_in: u64,
508+
minimum_lamports_out: u64,
509+
},
505510
}
506511

507512
pub const STAKE_POOL_DEPOSIT_STAKE_AUTHORITY: &[u8] = b"deposit_stake_authority";

0 commit comments

Comments
 (0)