Skip to content

Commit f213496

Browse files
committed
withdraw tests
1 parent f9b3fa7 commit f213496

2 files changed

Lines changed: 142 additions & 1 deletion

File tree

program/src/processor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ impl Processor {
12401240
calculate_withdraw_amount(token_supply, pre_total_nev, token_amount)
12411241
.ok_or(SinglePoolError::UnexpectedMathError)?;
12421242

1243-
// self-explanatory
1243+
// self-explanatory. we catch 0 deposit above so we only hit this if we rounded to 0
12441244
if stake_to_withdraw == 0 {
12451245
return Err(SinglePoolError::WithdrawalTooSmall.into());
12461246
}

program/tests/withdraw.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ mod helpers;
33

44
use {
55
helpers::*,
6+
solana_program_pack::Pack,
67
solana_program_test::*,
78
solana_signer::Signer,
89
solana_transaction::Transaction,
910
spl_single_pool::{error::SinglePoolError, id, instruction},
11+
spl_token_interface::state::Mint,
1012
test_case::{test_case, test_matrix},
1113
};
1214

@@ -358,3 +360,142 @@ async fn success_withdraw_from_inactive(stake_version: StakeProgramVersion) {
358360
TEST_STAKE_AMOUNT + get_stake_account_rent(&mut context.banks_client).await
359361
);
360362
}
363+
364+
#[test_matrix(
365+
[StakeProgramVersion::Stable, StakeProgramVersion::Beta, StakeProgramVersion::Edge]
366+
)]
367+
#[tokio::test]
368+
async fn fail_disallowed_withdraw(stake_version: StakeProgramVersion) {
369+
let Some(program_test) = program_test(stake_version) else {
370+
return;
371+
};
372+
let mut context = program_test.start_with_context().await;
373+
374+
let accounts = SinglePoolAccounts::default();
375+
accounts
376+
.initialize_for_withdraw(&mut context, TEST_STAKE_AMOUNT, None, true)
377+
.await;
378+
379+
let minimum_delegation = get_minimum_delegation(
380+
&mut context.banks_client,
381+
&context.payer,
382+
&context.last_blockhash,
383+
)
384+
.await;
385+
386+
// actually 0 withdraw fails
387+
let instructions = instruction::withdraw(
388+
&id(),
389+
&accounts.pool,
390+
&accounts.alice_stake.pubkey(),
391+
&accounts.alice.pubkey(),
392+
&accounts.alice_token,
393+
&accounts.alice.pubkey(),
394+
0,
395+
);
396+
let transaction = Transaction::new_signed_with_payer(
397+
&instructions,
398+
Some(&accounts.alice.pubkey()),
399+
&[&accounts.alice],
400+
context.last_blockhash,
401+
);
402+
403+
let e = context
404+
.banks_client
405+
.process_transaction(transaction)
406+
.await
407+
.unwrap_err();
408+
check_error(e, SinglePoolError::WithdrawalTooSmall);
409+
410+
// sub-minimum delegation withdraw fails
411+
let instructions = instruction::withdraw(
412+
&id(),
413+
&accounts.pool,
414+
&accounts.alice_stake.pubkey(),
415+
&accounts.alice.pubkey(),
416+
&accounts.alice_token,
417+
&accounts.alice.pubkey(),
418+
minimum_delegation - 1,
419+
);
420+
let transaction = Transaction::new_signed_with_payer(
421+
&instructions,
422+
Some(&accounts.alice.pubkey()),
423+
&[&accounts.alice],
424+
context.last_blockhash,
425+
);
426+
427+
let e = context
428+
.banks_client
429+
.process_transaction(transaction)
430+
.await
431+
.unwrap_err();
432+
check_error(e, SinglePoolError::WithdrawalTooSmall);
433+
434+
// pump NEV higher. token is worth more but mostly backed by liquid sol
435+
transfer(
436+
&mut context.banks_client,
437+
&context.payer,
438+
&context.last_blockhash,
439+
&accounts.stake_account,
440+
TEST_STAKE_AMOUNT * 10,
441+
)
442+
.await;
443+
444+
// withdrawal that cannot be delivered as stake fails
445+
let instructions = instruction::withdraw(
446+
&id(),
447+
&accounts.pool,
448+
&accounts.alice_stake.pubkey(),
449+
&accounts.alice.pubkey(),
450+
&accounts.alice_token,
451+
&accounts.alice.pubkey(),
452+
TEST_STAKE_AMOUNT,
453+
);
454+
let transaction = Transaction::new_signed_with_payer(
455+
&instructions,
456+
Some(&accounts.alice.pubkey()),
457+
&[&accounts.alice],
458+
context.last_blockhash,
459+
);
460+
461+
let e = context
462+
.banks_client
463+
.process_transaction(transaction)
464+
.await
465+
.unwrap_err();
466+
check_error(e, SinglePoolError::WithdrawalViolatesPoolRequirements);
467+
468+
// slash the pool percentage that one token represents
469+
let mut mint_account = get_account(&mut context.banks_client, &accounts.mint).await;
470+
let mut mint_data = Mint::unpack_from_slice(&mint_account.data).unwrap();
471+
mint_data.supply *= 100;
472+
Mint::pack(mint_data, &mut mint_account.data).unwrap();
473+
context.set_account(&accounts.mint, &mint_account.into());
474+
475+
// the minimum withdrawal from a non-inactve pool can only round to 0 if a pool has >1 billion sol
476+
force_deactivate_stake_account(&mut context, &accounts.stake_account).await;
477+
478+
// withdrawal that rounds to 0 fails
479+
let instructions = instruction::withdraw(
480+
&id(),
481+
&accounts.pool,
482+
&accounts.alice_stake.pubkey(),
483+
&accounts.alice.pubkey(),
484+
&accounts.alice_token,
485+
&accounts.alice.pubkey(),
486+
1,
487+
);
488+
let transaction = Transaction::new_signed_with_payer(
489+
&instructions,
490+
Some(&accounts.alice.pubkey()),
491+
&[&accounts.alice],
492+
context.last_blockhash,
493+
);
494+
495+
let e = context
496+
.banks_client
497+
.process_transaction(transaction)
498+
.await
499+
.unwrap_err();
500+
check_error(e, SinglePoolError::WithdrawalTooSmall);
501+
}

0 commit comments

Comments
 (0)