| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465 |
- #![allow(clippy::arithmetic_side_effects)]
- use {
- assert_matches::assert_matches,
- solana_account::state_traits::StateMut,
- solana_cli::{
- check_balance,
- cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
- spend_utils::SpendAmount,
- stake::StakeAuthorizationIndexed,
- test_utils::{check_ready, wait_for_next_epoch_plus_n_slots},
- },
- solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
- solana_commitment_config::CommitmentConfig,
- solana_epoch_schedule::EpochSchedule,
- solana_faucet::faucet::run_local_faucet,
- solana_fee_calculator::FeeRateGovernor,
- solana_fee_structure::FeeStructure,
- solana_keypair::{keypair_from_seed, Keypair},
- solana_native_token::LAMPORTS_PER_SOL,
- solana_nonce::state::State as NonceState,
- solana_pubkey::Pubkey,
- solana_rent::Rent,
- solana_rpc_client::rpc_client::RpcClient,
- solana_rpc_client_api::request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
- solana_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery},
- solana_signer::Signer,
- solana_stake_interface::{
- self as stake,
- instruction::LockupArgs,
- state::{Lockup, StakeAuthorize, StakeStateV2},
- },
- solana_streamer::socket::SocketAddrSpace,
- solana_test_validator::{TestValidator, TestValidatorGenesis},
- test_case::test_case,
- };
- #[test]
- fn test_stake_delegation_force() {
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let authorized_withdrawer = Keypair::new().pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let slots_per_epoch = 32;
- let test_validator = TestValidatorGenesis::default()
- .fee_rate_governor(FeeRateGovernor::new(0, 0))
- .rent(Rent {
- lamports_per_byte_year: 1,
- exemption_threshold: 1.0,
- ..Rent::default()
- })
- .epoch_schedule(EpochSchedule::custom(
- slots_per_epoch,
- slots_per_epoch,
- /* enable_warmup_epochs = */ false,
- ))
- .faucet_addr(Some(faucet_addr))
- .warp_slot(DELINQUENT_VALIDATOR_SLOT_DISTANCE * 2) // get out in front of the cli voter delinquency check
- .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
- .expect("validator start failed");
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.signers = vec![&default_signer];
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- // Create vote account
- let vote_keypair = Keypair::new();
- config.signers = vec![&default_signer, &vote_keypair];
- config.command = CliCommand::CreateVoteAccount {
- vote_account: 1,
- seed: None,
- identity_account: 0,
- authorized_voter: None,
- authorized_withdrawer,
- commission: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- // Create stake account
- let stake_keypair = Keypair::new();
- config.signers = vec![&default_signer, &stake_keypair];
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(25_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- // Delegate stake succeeds despite no votes, because voter has zero stake
- config.signers = vec![&default_signer];
- config.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: vote_keypair.pubkey(),
- stake_authority: 0,
- force: false,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- // Create a second stake account
- let stake_keypair2 = Keypair::new();
- config.signers = vec![&default_signer, &stake_keypair2];
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(25_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- wait_for_next_epoch_plus_n_slots(&rpc_client, 1);
- // Delegate stake2 fails because voter has not voted, but is now staked
- config.signers = vec![&default_signer];
- config.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair2.pubkey(),
- vote_account_pubkey: vote_keypair.pubkey(),
- stake_authority: 0,
- force: false,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap_err();
- // But if we force it, it works anyway!
- config.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair2.pubkey(),
- vote_account_pubkey: vote_keypair.pubkey(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_seed_stake_delegation_and_deactivation(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let validator_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let mut config_validator = CliConfig::recent_for_tests();
- config_validator.json_rpc_url = test_validator.rpc_url();
- config_validator.signers = vec![&validator_keypair];
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &config_validator.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- check_balance!(
- 100_000_000_000,
- &rpc_client,
- &config_validator.signers[0].pubkey()
- );
- let stake_address = Pubkey::create_with_seed(
- &config_validator.signers[0].pubkey(),
- "hi there",
- &stake::program::id(),
- )
- .expect("bad seed");
- // Create stake account with a seed, uses the validator config as the base,
- // which is nice ;)
- config_validator.command = CliCommand::CreateStakeAccount {
- stake_account: 0,
- seed: Some("hi there".to_string()),
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- // Delegate stake
- config_validator.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_address,
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- // Deactivate stake
- config_validator.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_address,
- stake_authority: 0,
- sign_only: false,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- }
- #[test]
- fn test_stake_delegation_and_withdraw_available() {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let validator_keypair = Keypair::new();
- let mut config_validator = CliConfig::recent_for_tests();
- config_validator.json_rpc_url = test_validator.rpc_url();
- config_validator.signers = vec![&validator_keypair];
- let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let recipient_pubkey = Pubkey::new_unique();
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &config_validator.signers[0].pubkey(),
- 100 * LAMPORTS_PER_SOL,
- )
- .unwrap();
- check_balance!(
- 100_000_000_000,
- &rpc_client,
- &config_validator.signers[0].pubkey()
- );
- // Create stake account
- config_validator.signers.push(&stake_keypair);
- config_validator.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50 * LAMPORTS_PER_SOL),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Delegate stake
- config_validator.signers.pop();
- config_validator.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Withdraw available stake
- config_validator.signers = vec![&validator_keypair];
- config_validator.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::Available,
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_authority: 0,
- nonce_account: None,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // While withdraw transaction succeeds, no lamports move because all stake
- // is activating
- check_balance!(0, &rpc_client, &recipient_pubkey);
- // Add extra SOL to the stake account
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &stake_keypair.pubkey(),
- 5 * LAMPORTS_PER_SOL,
- )
- .unwrap();
- check_balance!(55 * LAMPORTS_PER_SOL, &rpc_client, &stake_keypair.pubkey());
- // Withdraw available stake
- config_validator.signers = vec![&validator_keypair];
- config_validator.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::Available,
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_authority: 0,
- nonce_account: None,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Extra (inactive) SOL is withdrawn
- check_balance!(5 * LAMPORTS_PER_SOL, &rpc_client, &recipient_pubkey);
- // Deactivate stake
- config_validator.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- stake_authority: 0,
- sign_only: false,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Withdraw available stake
- config_validator.signers = vec![&validator_keypair];
- config_validator.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::Available,
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_authority: 0,
- nonce_account: None,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Complete balance is withdrawn because all stake is inactive
- check_balance!(55 * LAMPORTS_PER_SOL, &rpc_client, &recipient_pubkey);
- }
- #[test]
- fn test_stake_delegation_and_withdraw_all() {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let validator_keypair = Keypair::new();
- let mut config_validator = CliConfig::recent_for_tests();
- config_validator.json_rpc_url = test_validator.rpc_url();
- config_validator.signers = vec![&validator_keypair];
- let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let recipient_pubkey = Pubkey::new_unique();
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &config_validator.signers[0].pubkey(),
- 100 * LAMPORTS_PER_SOL,
- )
- .unwrap();
- check_balance!(
- 100_000_000_000,
- &rpc_client,
- &config_validator.signers[0].pubkey()
- );
- // Create stake account
- config_validator.signers.push(&stake_keypair);
- config_validator.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50 * LAMPORTS_PER_SOL),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Delegate stake
- config_validator.signers.pop();
- config_validator.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Withdraw all stake fails because stake is activating
- config_validator.signers = vec![&validator_keypair];
- config_validator.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::All,
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_authority: 0,
- nonce_account: None,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap_err();
- // Add extra SOL to the stake account
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &stake_keypair.pubkey(),
- 5 * LAMPORTS_PER_SOL,
- )
- .unwrap();
- check_balance!(55 * LAMPORTS_PER_SOL, &rpc_client, &stake_keypair.pubkey());
- // Withdraw all stake still fails, because it attempts to withdraw both
- // activating and inactive stake
- config_validator.signers = vec![&validator_keypair];
- config_validator.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::All,
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_authority: 0,
- nonce_account: None,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap_err();
- // Deactivate stake
- config_validator.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- stake_authority: 0,
- sign_only: false,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- // Withdraw stake
- config_validator.signers = vec![&validator_keypair];
- config_validator.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::All,
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_authority: 0,
- nonce_account: None,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config_validator).unwrap();
- check_balance!(55 * LAMPORTS_PER_SOL, &rpc_client, &recipient_pubkey);
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_stake_delegation_and_deactivation(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let validator_keypair = Keypair::new();
- let mut config_validator = CliConfig::recent_for_tests();
- config_validator.json_rpc_url = test_validator.rpc_url();
- config_validator.signers = vec![&validator_keypair];
- let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &config_validator.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- check_balance!(
- 100_000_000_000,
- &rpc_client,
- &config_validator.signers[0].pubkey()
- );
- // Create stake account
- config_validator.signers.push(&stake_keypair);
- config_validator.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- // Delegate stake
- config_validator.signers.pop();
- config_validator.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- // Deactivate stake
- config_validator.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- stake_authority: 0,
- sign_only: false,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_offline_stake_delegation_and_deactivation(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let mut config_validator = CliConfig::recent_for_tests();
- config_validator.json_rpc_url = test_validator.rpc_url();
- let validator_keypair = Keypair::new();
- config_validator.signers = vec![&validator_keypair];
- let mut config_payer = CliConfig::recent_for_tests();
- config_payer.json_rpc_url = test_validator.rpc_url();
- let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let mut config_offline = CliConfig::recent_for_tests();
- config_offline.json_rpc_url = String::default();
- config_offline.command = CliCommand::ClusterVersion;
- let offline_keypair = Keypair::new();
- config_offline.signers = vec![&offline_keypair];
- // Verify that we cannot reach the cluster
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(
- &rpc_client,
- &config_validator,
- &config_validator.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- check_balance!(
- 100_000_000_000,
- &rpc_client,
- &config_validator.signers[0].pubkey()
- );
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &config_offline.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- check_balance!(
- 100_000_000_000,
- &rpc_client,
- &config_offline.signers[0].pubkey()
- );
- // Create stake account
- config_validator.signers.push(&stake_keypair);
- config_validator.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: Some(config_offline.signers[0].pubkey()),
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config_validator).unwrap();
- // Delegate stake offline
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- config_offline.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only
- .presigner_of(&config_offline.signers[0].pubkey())
- .unwrap();
- config_payer.signers = vec![&offline_presigner];
- config_payer.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- // Deactivate stake offline
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- config_offline.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- stake_authority: 0,
- sign_only: true,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only
- .presigner_of(&config_offline.signers[0].pubkey())
- .unwrap();
- config_payer.signers = vec![&offline_presigner];
- config_payer.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- stake_authority: 0,
- sign_only: false,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_nonced_stake_delegation_and_deactivation(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let config_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let mut config = CliConfig::recent_for_tests();
- config.signers = vec![&config_keypair];
- config.json_rpc_url = test_validator.rpc_url();
- let minimum_nonce_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(NonceState::size())
- .unwrap();
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- // Create stake account
- let stake_keypair = Keypair::new();
- config.signers.push(&stake_keypair);
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Create nonce account
- let nonce_account = Keypair::new();
- config.signers[1] = &nonce_account;
- config.command = CliCommand::CreateNonceAccount {
- nonce_account: 1,
- seed: None,
- nonce_authority: Some(config.signers[0].pubkey()),
- memo: None,
- amount: SpendAmount::Some(minimum_nonce_balance),
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Delegate stake
- config.signers = vec![&config_keypair];
- config.command = CliCommand::DelegateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- vote_account_pubkey: test_validator.vote_account_address(),
- stake_authority: 0,
- force: true,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
- nonce_hash,
- ),
- nonce_account: Some(nonce_account.pubkey()),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Deactivate stake
- config.command = CliCommand::DeactivateStake {
- stake_account_pubkey: stake_keypair.pubkey(),
- stake_authority: 0,
- sign_only: false,
- deactivate_delinquent: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
- nonce_hash,
- ),
- nonce_account: Some(nonce_account.pubkey()),
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_stake_authorize(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.signers = vec![&default_signer];
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- let offline_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let mut config_offline = CliConfig::recent_for_tests();
- config_offline.signers = vec![&offline_keypair];
- config_offline.json_rpc_url = String::default();
- let offline_authority_pubkey = config_offline.signers[0].pubkey();
- config_offline.command = CliCommand::ClusterVersion;
- // Verify that we cannot reach the cluster
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &config_offline.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- // Create stake account, identity is authority
- let stake_keypair = Keypair::new();
- let stake_account_pubkey = stake_keypair.pubkey();
- config.signers.push(&stake_keypair);
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Assign new online stake authority
- let online_authority = Keypair::new();
- let online_authority_pubkey = online_authority.pubkey();
- config.signers.pop();
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: online_authority_pubkey,
- authority: 0,
- new_authority_signer: None,
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_authority = match stake_state {
- StakeStateV2::Initialized(meta) => meta.authorized.staker,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_authority, online_authority_pubkey);
- // Assign new online stake and withdraw authorities
- let online_authority2 = Keypair::new();
- let online_authority2_pubkey = online_authority2.pubkey();
- let withdraw_authority = Keypair::new();
- let withdraw_authority_pubkey = withdraw_authority.pubkey();
- config.signers.push(&online_authority);
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![
- StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: online_authority2_pubkey,
- authority: 1,
- new_authority_signer: None,
- },
- StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Withdrawer,
- new_authority_pubkey: withdraw_authority_pubkey,
- authority: 0,
- new_authority_signer: None,
- },
- ],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let (current_staker, current_withdrawer) = match stake_state {
- StakeStateV2::Initialized(meta) => (meta.authorized.staker, meta.authorized.withdrawer),
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_staker, online_authority2_pubkey);
- assert_eq!(current_withdrawer, withdraw_authority_pubkey);
- // Assign new offline stake authority
- config.signers.pop();
- config.signers.push(&online_authority2);
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: offline_authority_pubkey,
- authority: 1,
- new_authority_signer: None,
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_authority = match stake_state {
- StakeStateV2::Initialized(meta) => meta.authorized.staker,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_authority, offline_authority_pubkey);
- // Offline assignment of new nonced stake authority
- let nonced_authority = Keypair::new();
- let nonced_authority_pubkey = nonced_authority.pubkey();
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- config_offline.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: nonced_authority_pubkey,
- authority: 0,
- new_authority_signer: None,
- }],
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sign_reply = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sign_reply);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only.presigner_of(&offline_authority_pubkey).unwrap();
- config.signers = vec![&offline_presigner];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: nonced_authority_pubkey,
- authority: 0,
- new_authority_signer: None,
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_authority = match stake_state {
- StakeStateV2::Initialized(meta) => meta.authorized.staker,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_authority, nonced_authority_pubkey);
- // Create nonce account
- let minimum_nonce_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(NonceState::size())
- .unwrap();
- let nonce_account = Keypair::new();
- config.signers = vec![&default_signer, &nonce_account];
- config.command = CliCommand::CreateNonceAccount {
- nonce_account: 1,
- seed: None,
- nonce_authority: Some(offline_authority_pubkey),
- memo: None,
- amount: SpendAmount::Some(minimum_nonce_balance),
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Nonced assignment of new online stake authority
- let online_authority = Keypair::new();
- let online_authority_pubkey = online_authority.pubkey();
- config_offline.signers.push(&nonced_authority);
- config_offline.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: online_authority_pubkey,
- authority: 1,
- new_authority_signer: None,
- }],
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(nonce_hash),
- nonce_account: Some(nonce_account.pubkey()),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- let sign_reply = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sign_reply);
- assert!(sign_only.has_all_signers());
- assert_eq!(sign_only.blockhash, nonce_hash);
- let offline_presigner = sign_only.presigner_of(&offline_authority_pubkey).unwrap();
- let nonced_authority_presigner = sign_only.presigner_of(&nonced_authority_pubkey).unwrap();
- config.signers = vec![&offline_presigner, &nonced_authority_presigner];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: online_authority_pubkey,
- authority: 1,
- new_authority_signer: None,
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
- sign_only.blockhash,
- ),
- nonce_account: Some(nonce_account.pubkey()),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_authority = match stake_state {
- StakeStateV2::Initialized(meta) => meta.authorized.staker,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_authority, online_authority_pubkey);
- let new_nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- assert_ne!(nonce_hash, new_nonce_hash);
- }
- #[test]
- fn test_stake_authorize_with_fee_payer() {
- solana_logger::setup();
- let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
- let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator = TestValidator::with_custom_fees(
- mint_pubkey,
- fee_one_sig,
- Some(faucet_addr),
- SocketAddrSpace::Unspecified,
- );
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let default_pubkey = default_signer.pubkey();
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.signers = vec![&default_signer];
- let payer_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let mut config_payer = CliConfig::recent_for_tests();
- config_payer.signers = vec![&payer_keypair];
- config_payer.json_rpc_url = test_validator.rpc_url();
- let payer_pubkey = config_payer.signers[0].pubkey();
- let mut config_offline = CliConfig::recent_for_tests();
- let offline_signer = Keypair::new();
- config_offline.signers = vec![&offline_signer];
- config_offline.json_rpc_url = String::new();
- let offline_pubkey = config_offline.signers[0].pubkey();
- // Verify we're offline
- config_offline.command = CliCommand::ClusterVersion;
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 5_000_000_000_000).unwrap();
- check_balance!(5_000_000_000_000, &rpc_client, &config.signers[0].pubkey());
- request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 5_000_000_000_000)
- .unwrap();
- check_balance!(5_000_000_000_000, &rpc_client, &payer_pubkey);
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &offline_pubkey,
- 5_000_000_000_000,
- )
- .unwrap();
- check_balance!(5_000_000_000_000, &rpc_client, &offline_pubkey);
- check_ready(&rpc_client);
- // Create stake account, identity is authority
- let stake_keypair = Keypair::new();
- let stake_account_pubkey = stake_keypair.pubkey();
- config.signers.push(&stake_keypair);
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(1_000_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- check_balance!(
- 4_000_000_000_000 - fee_two_sig,
- &rpc_client,
- &default_pubkey
- );
- // Assign authority with separate fee payer
- config.signers = vec![&default_signer, &payer_keypair];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: offline_pubkey,
- authority: 0,
- new_authority_signer: None,
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 1,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- // `config` balance has not changed, despite submitting the TX
- check_balance!(
- 4_000_000_000_000 - fee_two_sig,
- &rpc_client,
- &default_pubkey
- );
- // `config_payer` however has paid `config`'s authority sig
- // and `config_payer`'s fee sig
- check_balance!(5_000_000_000_000 - fee_two_sig, &rpc_client, &payer_pubkey);
- // Assign authority with offline fee payer
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- config_offline.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: payer_pubkey,
- authority: 0,
- new_authority_signer: None,
- }],
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sign_reply = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sign_reply);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
- config.signers = vec![&offline_presigner];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: payer_pubkey,
- authority: 0,
- new_authority_signer: None,
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- // `config`'s balance again has not changed
- check_balance!(
- 4_000_000_000_000 - fee_two_sig,
- &rpc_client,
- &default_pubkey
- );
- // `config_offline` however has paid 1 sig due to being both authority
- // and fee payer
- check_balance!(
- 5_000_000_000_000 - fee_one_sig,
- &rpc_client,
- &offline_pubkey
- );
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_stake_split(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator = TestValidator::with_custom_fees(
- mint_pubkey,
- 1,
- Some(faucet_addr),
- SocketAddrSpace::Unspecified,
- );
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let offline_signer = Keypair::new();
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.signers = vec![&default_signer];
- let minimum_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
- .unwrap();
- let mut config_offline = CliConfig::recent_for_tests();
- config_offline.json_rpc_url = String::default();
- config_offline.signers = vec![&offline_signer];
- let offline_pubkey = config_offline.signers[0].pubkey();
- // Verify we're offline
- config_offline.command = CliCommand::ClusterVersion;
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 50_000_000_000_000,
- )
- .unwrap();
- check_balance!(50_000_000_000_000, &rpc_client, &config.signers[0].pubkey());
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &offline_pubkey,
- 1_000_000_000_000,
- )
- .unwrap();
- check_balance!(1_000_000_000_000, &rpc_client, &offline_pubkey);
- // Create stake account, identity is authority
- let stake_balance = minimum_balance + 10_000_000_000;
- let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let stake_account_pubkey = stake_keypair.pubkey();
- config.signers.push(&stake_keypair);
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: Some(offline_pubkey),
- withdrawer: Some(offline_pubkey),
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(10 * stake_balance),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(10 * stake_balance, &rpc_client, &stake_account_pubkey,);
- // Create nonce account
- let minimum_nonce_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(NonceState::size())
- .unwrap();
- let nonce_account = keypair_from_seed(&[1u8; 32]).unwrap();
- config.signers = vec![&default_signer, &nonce_account];
- config.command = CliCommand::CreateNonceAccount {
- nonce_account: 1,
- seed: None,
- nonce_authority: Some(offline_pubkey),
- memo: None,
- amount: SpendAmount::Some(minimum_nonce_balance),
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Nonced offline split
- let split_account = keypair_from_seed(&[2u8; 32]).unwrap();
- check_balance!(0, &rpc_client, &split_account.pubkey());
- config_offline.signers.push(&split_account);
- config_offline.command = CliCommand::SplitStake {
- stake_account_pubkey,
- stake_authority: 0,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(nonce_hash),
- nonce_account: Some(nonce_account.pubkey()),
- nonce_authority: 0,
- memo: None,
- split_stake_account: 1,
- seed: None,
- lamports: 2 * stake_balance,
- fee_payer: 0,
- compute_unit_price,
- rent_exempt_reserve: Some(minimum_balance),
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
- config.signers = vec![&offline_presigner, &split_account];
- config.command = CliCommand::SplitStake {
- stake_account_pubkey,
- stake_authority: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
- sign_only.blockhash,
- ),
- nonce_account: Some(nonce_account.pubkey()),
- nonce_authority: 0,
- memo: None,
- split_stake_account: 1,
- seed: None,
- lamports: 2 * stake_balance,
- fee_payer: 0,
- compute_unit_price,
- rent_exempt_reserve: None,
- };
- process_command(&config).unwrap();
- check_balance!(8 * stake_balance, &rpc_client, &stake_account_pubkey);
- check_balance!(
- 2 * stake_balance + minimum_balance,
- &rpc_client,
- &split_account.pubkey()
- );
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_stake_set_lockup(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator = TestValidator::with_custom_fees(
- mint_pubkey,
- 1,
- Some(faucet_addr),
- SocketAddrSpace::Unspecified,
- );
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let offline_signer = Keypair::new();
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.signers = vec![&default_signer];
- let mut config_offline = CliConfig::recent_for_tests();
- config_offline.json_rpc_url = String::default();
- config_offline.signers = vec![&offline_signer];
- let offline_pubkey = config_offline.signers[0].pubkey();
- // Verify we're offline
- config_offline.command = CliCommand::ClusterVersion;
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 5_000_000_000_000,
- )
- .unwrap();
- check_balance!(5_000_000_000_000, &rpc_client, &config.signers[0].pubkey());
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &offline_pubkey,
- 1_000_000_000_000,
- )
- .unwrap();
- check_balance!(1_000_000_000_000, &rpc_client, &offline_pubkey);
- // Create stake account, identity is authority
- let stake_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
- .unwrap()
- + 10_000_000_000;
- let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
- let stake_account_pubkey = stake_keypair.pubkey();
- let lockup = Lockup {
- custodian: config.signers[0].pubkey(),
- ..Lockup::default()
- };
- config.signers.push(&stake_keypair);
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: Some(offline_pubkey),
- withdrawer: Some(config.signers[0].pubkey()),
- withdrawer_signer: None,
- lockup,
- amount: SpendAmount::Some(10 * stake_balance),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(10 * stake_balance, &rpc_client, &stake_account_pubkey,);
- // Online set lockup
- let lockup = LockupArgs {
- unix_timestamp: Some(1_581_534_570),
- epoch: Some(200),
- custodian: None,
- };
- config.signers.pop();
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: None,
- custodian: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_lockup = match stake_state {
- StakeStateV2::Initialized(meta) => meta.lockup,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(
- current_lockup.unix_timestamp,
- lockup.unix_timestamp.unwrap()
- );
- assert_eq!(current_lockup.epoch, lockup.epoch.unwrap());
- assert_eq!(current_lockup.custodian, config.signers[0].pubkey());
- // Set custodian to another pubkey
- let online_custodian = Keypair::new();
- let online_custodian_pubkey = online_custodian.pubkey();
- let lockup = LockupArgs {
- unix_timestamp: Some(1_581_534_571),
- epoch: Some(201),
- custodian: Some(online_custodian_pubkey),
- };
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: None,
- custodian: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let lockup = LockupArgs {
- unix_timestamp: Some(1_581_534_572),
- epoch: Some(202),
- custodian: None,
- };
- config.signers = vec![&default_signer, &online_custodian];
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: None,
- custodian: 1,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_lockup = match stake_state {
- StakeStateV2::Initialized(meta) => meta.lockup,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(
- current_lockup.unix_timestamp,
- lockup.unix_timestamp.unwrap()
- );
- assert_eq!(current_lockup.epoch, lockup.epoch.unwrap());
- assert_eq!(current_lockup.custodian, online_custodian_pubkey);
- // Set custodian to offline pubkey
- let lockup = LockupArgs {
- unix_timestamp: Some(1_581_534_573),
- epoch: Some(203),
- custodian: Some(offline_pubkey),
- };
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: None,
- custodian: 1,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Create nonce account
- let minimum_nonce_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(NonceState::size())
- .unwrap();
- let nonce_account = keypair_from_seed(&[1u8; 32]).unwrap();
- let nonce_account_pubkey = nonce_account.pubkey();
- config.signers = vec![&default_signer, &nonce_account];
- config.command = CliCommand::CreateNonceAccount {
- nonce_account: 1,
- seed: None,
- nonce_authority: Some(offline_pubkey),
- memo: None,
- amount: SpendAmount::Some(minimum_nonce_balance),
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey);
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Nonced offline set lockup
- let lockup = LockupArgs {
- unix_timestamp: Some(1_581_534_576),
- epoch: Some(222),
- custodian: None,
- };
- config_offline.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: None,
- custodian: 0,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(nonce_hash),
- nonce_account: Some(nonce_account_pubkey),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
- config.signers = vec![&offline_presigner];
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: None,
- custodian: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_account_pubkey),
- sign_only.blockhash,
- ),
- nonce_account: Some(nonce_account_pubkey),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_lockup = match stake_state {
- StakeStateV2::Initialized(meta) => meta.lockup,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(
- current_lockup.unix_timestamp,
- lockup.unix_timestamp.unwrap()
- );
- assert_eq!(current_lockup.epoch, lockup.epoch.unwrap());
- assert_eq!(current_lockup.custodian, offline_pubkey);
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_offline_nonced_create_stake_account_and_withdraw(compute_unit_price: Option<u64>) {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let mut config = CliConfig::recent_for_tests();
- let default_signer = keypair_from_seed(&[1u8; 32]).unwrap();
- config.signers = vec![&default_signer];
- config.json_rpc_url = test_validator.rpc_url();
- let mut config_offline = CliConfig::recent_for_tests();
- let offline_signer = keypair_from_seed(&[2u8; 32]).unwrap();
- config_offline.signers = vec![&offline_signer];
- let offline_pubkey = config_offline.signers[0].pubkey();
- config_offline.json_rpc_url = String::default();
- config_offline.command = CliCommand::ClusterVersion;
- // Verify that we cannot reach the cluster
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 200_000_000_000,
- )
- .unwrap();
- check_balance!(200_000_000_000, &rpc_client, &config.signers[0].pubkey());
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &offline_pubkey,
- 100_000_000_000,
- )
- .unwrap();
- check_balance!(100_000_000_000, &rpc_client, &offline_pubkey);
- // Create nonce account
- let minimum_nonce_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(NonceState::size())
- .unwrap();
- let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
- let nonce_pubkey = nonce_account.pubkey();
- config.signers.push(&nonce_account);
- config.command = CliCommand::CreateNonceAccount {
- nonce_account: 1,
- seed: None,
- nonce_authority: Some(offline_pubkey),
- memo: None,
- amount: SpendAmount::Some(minimum_nonce_balance),
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Create stake account offline
- let stake_keypair = keypair_from_seed(&[4u8; 32]).unwrap();
- let stake_pubkey = stake_keypair.pubkey();
- config_offline.signers.push(&stake_keypair);
- config_offline.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(nonce_hash),
- nonce_account: Some(nonce_pubkey),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
- let stake_presigner = sign_only.presigner_of(&stake_pubkey).unwrap();
- config.signers = vec![&offline_presigner, &stake_presigner];
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: Some(offline_pubkey),
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_pubkey),
- sign_only.blockhash,
- ),
- nonce_account: Some(nonce_pubkey),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(50_000_000_000, &rpc_client, &stake_pubkey);
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Offline, nonced stake-withdraw
- let recipient = keypair_from_seed(&[5u8; 32]).unwrap();
- let recipient_pubkey = recipient.pubkey();
- config_offline.signers.pop();
- config_offline.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_pubkey,
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::Some(50_000_000_000),
- withdraw_authority: 0,
- custodian: None,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(nonce_hash),
- nonce_account: Some(nonce_pubkey),
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
- config.signers = vec![&offline_presigner];
- config.command = CliCommand::WithdrawStake {
- stake_account_pubkey: stake_pubkey,
- destination_account_pubkey: recipient_pubkey,
- amount: SpendAmount::Some(50_000_000_000),
- withdraw_authority: 0,
- custodian: None,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_pubkey),
- sign_only.blockhash,
- ),
- nonce_account: Some(nonce_pubkey),
- nonce_authority: 0,
- memo: None,
- seed: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(50_000_000_000, &rpc_client, &recipient_pubkey);
- // Fetch nonce hash
- let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
- &rpc_client,
- &nonce_account.pubkey(),
- CommitmentConfig::processed(),
- )
- .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
- .unwrap()
- .blockhash();
- // Create another stake account. This time with seed
- let seed = "seedy";
- config_offline.signers = vec![&offline_signer, &stake_keypair];
- config_offline.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: Some(seed.to_string()),
- staker: None,
- withdrawer: None,
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(nonce_hash),
- nonce_account: Some(nonce_pubkey),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
- let stake_presigner = sign_only.presigner_of(&stake_pubkey).unwrap();
- config.signers = vec![&offline_presigner, &stake_presigner];
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: Some(seed.to_string()),
- staker: Some(offline_pubkey),
- withdrawer: Some(offline_pubkey),
- withdrawer_signer: None,
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(
- blockhash_query::Source::NonceAccount(nonce_pubkey),
- sign_only.blockhash,
- ),
- nonce_account: Some(nonce_pubkey),
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let seed_address =
- Pubkey::create_with_seed(&stake_pubkey, seed, &stake::program::id()).unwrap();
- check_balance!(50_000_000_000, &rpc_client, &seed_address);
- }
- #[test]
- fn test_stake_checked_instructions() {
- solana_logger::setup();
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet(mint_keypair, None);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.signers = vec![&default_signer];
- request_and_confirm_airdrop(
- &rpc_client,
- &config,
- &config.signers[0].pubkey(),
- 100_000_000_000,
- )
- .unwrap();
- // Create stake account with withdrawer
- let stake_keypair = Keypair::new();
- let stake_account_pubkey = stake_keypair.pubkey();
- let withdrawer_keypair = Keypair::new();
- let withdrawer_pubkey = withdrawer_keypair.pubkey();
- config.signers.push(&stake_keypair);
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: Some(withdrawer_pubkey),
- withdrawer_signer: Some(1),
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap_err(); // unsigned authority should fail
- config.signers = vec![&default_signer, &stake_keypair, &withdrawer_keypair];
- config.command = CliCommand::CreateStakeAccount {
- stake_account: 1,
- seed: None,
- staker: None,
- withdrawer: Some(withdrawer_pubkey),
- withdrawer_signer: Some(1),
- lockup: Lockup::default(),
- amount: SpendAmount::Some(50_000_000_000),
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- from: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- // Re-authorize account, checking new authority
- let staker_keypair = Keypair::new();
- let staker_pubkey = staker_keypair.pubkey();
- config.signers = vec![&default_signer];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: staker_pubkey,
- authority: 0,
- new_authority_signer: Some(0),
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- process_command(&config).unwrap_err(); // unsigned authority should fail
- config.signers = vec![&default_signer, &staker_keypair];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Staker,
- new_authority_pubkey: staker_pubkey,
- authority: 0,
- new_authority_signer: Some(1),
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_authority = match stake_state {
- StakeStateV2::Initialized(meta) => meta.authorized.staker,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_authority, staker_pubkey);
- let new_withdrawer_keypair = Keypair::new();
- let new_withdrawer_pubkey = new_withdrawer_keypair.pubkey();
- config.signers = vec![&default_signer, &withdrawer_keypair];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Withdrawer,
- new_authority_pubkey: new_withdrawer_pubkey,
- authority: 1,
- new_authority_signer: Some(1),
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- process_command(&config).unwrap_err(); // unsigned authority should fail
- config.signers = vec![
- &default_signer,
- &withdrawer_keypair,
- &new_withdrawer_keypair,
- ];
- config.command = CliCommand::StakeAuthorize {
- stake_account_pubkey,
- new_authorizations: vec![StakeAuthorizationIndexed {
- authorization_type: StakeAuthorize::Withdrawer,
- new_authority_pubkey: new_withdrawer_pubkey,
- authority: 1,
- new_authority_signer: Some(2),
- }],
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- custodian: None,
- no_wait: false,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_authority = match stake_state {
- StakeStateV2::Initialized(meta) => meta.authorized.withdrawer,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(current_authority, new_withdrawer_pubkey);
- // Set lockup, checking new custodian
- let custodian = Keypair::new();
- let custodian_pubkey = custodian.pubkey();
- let lockup = LockupArgs {
- unix_timestamp: Some(1_581_534_570),
- epoch: Some(200),
- custodian: Some(custodian_pubkey),
- };
- config.signers = vec![&default_signer, &new_withdrawer_keypair];
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: Some(1),
- custodian: 1,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap_err(); // unsigned new custodian should fail
- config.signers = vec![&default_signer, &new_withdrawer_keypair, &custodian];
- config.command = CliCommand::StakeSetLockup {
- stake_account_pubkey,
- lockup,
- new_custodian_signer: Some(2),
- custodian: 1,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::default(),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price: None,
- };
- process_command(&config).unwrap();
- let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
- let stake_state: StakeStateV2 = stake_account.state().unwrap();
- let current_lockup = match stake_state {
- StakeStateV2::Initialized(meta) => meta.lockup,
- _ => panic!("Unexpected stake state!"),
- };
- assert_eq!(
- current_lockup.unix_timestamp,
- lockup.unix_timestamp.unwrap()
- );
- assert_eq!(current_lockup.epoch, lockup.epoch.unwrap());
- assert_eq!(current_lockup.custodian, custodian_pubkey);
- }
- #[test]
- fn test_stake_minimum_delegation() {
- let test_validator =
- TestValidator::with_no_fees(Pubkey::new_unique(), None, SocketAddrSpace::Unspecified);
- let mut config = CliConfig::recent_for_tests();
- config.json_rpc_url = test_validator.rpc_url();
- config.command = CliCommand::StakeMinimumDelegation {
- use_lamports_unit: true,
- };
- let result = process_command(&config);
- assert_matches!(result, Ok(..));
- }
|