@@ -3,10 +3,12 @@ mod helpers;
33
44use {
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