#![allow(clippy::integer_arithmetic)] use gemachain_runtime::{ bank::Bank, bank_client::BankClient, genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}, }; use gemachain_sdk::{ account::from_account, account_utils::StateMut, client::SyncClient, message::Message, pubkey::Pubkey, signature::{Keypair, Signer}, stake::{ self, instruction as stake_instruction, state::{Authorized, Lockup, StakeState}, }, sysvar::{self, stake_history::StakeHistory}, }; use gemachain_stake_program::stake_state; use gemachain_vote_program::{ vote_instruction, vote_state::{Vote, VoteInit, VoteState, VoteStateVersions}, }; use std::sync::Arc; fn next_epoch(bank: &Arc) -> Arc { bank.squash(); Arc::new(Bank::new_from_parent( bank, &Pubkey::default(), bank.get_slots_in_epoch(bank.epoch()) + bank.slot(), )) } fn fill_epoch_with_votes( bank: &Arc, vote_keypair: &Keypair, mint_keypair: &Keypair, ) -> Arc { let mint_pubkey = mint_keypair.pubkey(); let vote_pubkey = vote_keypair.pubkey(); let old_epoch = bank.epoch(); let mut bank = bank.clone(); while bank.epoch() != old_epoch + 1 { bank.squash(); bank = Arc::new(Bank::new_from_parent( &bank, &Pubkey::default(), 1 + bank.slot(), )); let bank_client = BankClient::new_shared(&bank); let parent = bank.parent().unwrap(); let message = Message::new( &[vote_instruction::vote( &vote_pubkey, &vote_pubkey, Vote::new(vec![parent.slot() as u64], parent.hash()), )], Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message(&[mint_keypair, vote_keypair], message) .is_ok()); } bank } fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool { let stake = stake_state::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap(); stake.delegation.stake == stake.stake( bank.epoch(), Some( &from_account::( &bank.get_account(&sysvar::stake_history::id()).unwrap(), ) .unwrap(), ), ) } fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 { stake_state::stake_from(&bank.get_account(stake_pubkey).unwrap()) .unwrap() .stake( bank.epoch(), Some( &from_account::( &bank.get_account(&sysvar::stake_history::id()).unwrap(), ) .unwrap(), ), ) } #[test] fn test_stake_create_and_split_single_signature() { gemachain_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair: staker_keypair, .. } = create_genesis_config_with_leader( 100_000_000_000, &gemachain_sdk::pubkey::new_rand(), 1_000_000, ); let staker_pubkey = staker_keypair.pubkey(); let bank_client = BankClient::new_shared(&Arc::new(Bank::new_for_tests(&genesis_config))); let stake_address = Pubkey::create_with_seed(&staker_pubkey, "stake", &stake::program::id()).unwrap(); let authorized = Authorized::auto(&staker_pubkey); let carats = 1_000_000; // Create stake account with seed let message = Message::new( &stake_instruction::create_account_with_seed( &staker_pubkey, // from &stake_address, // to &staker_pubkey, // base "stake", // seed &authorized, &Lockup::default(), carats, ), Some(&staker_pubkey), ); // only one signature required bank_client .send_and_confirm_message(&[&staker_keypair], message) .expect("failed to create and delegate stake account"); // split the stake let split_stake_address = Pubkey::create_with_seed(&staker_pubkey, "split_stake", &stake::program::id()).unwrap(); // Test split let message = Message::new( &stake_instruction::split_with_seed( &stake_address, // original &staker_pubkey, // authorized carats / 2, &split_stake_address, // new address &staker_pubkey, // base "split_stake", // seed ), Some(&staker_keypair.pubkey()), ); assert!(bank_client .send_and_confirm_message(&[&staker_keypair], message) .is_ok()); // w00t! } #[test] fn test_stake_create_and_split_to_existing_system_account() { // Ensure stake-split allows the user to promote an existing system account into // a stake account. gemachain_logger::setup(); let GenesisConfigInfo { genesis_config, mint_keypair: staker_keypair, .. } = create_genesis_config_with_leader( 100_000_000_000, &gemachain_sdk::pubkey::new_rand(), 1_000_000, ); let staker_pubkey = staker_keypair.pubkey(); let bank_client = BankClient::new_shared(&Arc::new(Bank::new_for_tests(&genesis_config))); let stake_address = Pubkey::create_with_seed(&staker_pubkey, "stake", &stake::program::id()).unwrap(); let authorized = Authorized::auto(&staker_pubkey); let carats = 1_000_000; // Create stake account with seed let message = Message::new( &stake_instruction::create_account_with_seed( &staker_pubkey, // from &stake_address, // to &staker_pubkey, // base "stake", // seed &authorized, &Lockup::default(), carats, ), Some(&staker_pubkey), ); bank_client .send_and_confirm_message(&[&staker_keypair], message) .expect("failed to create and delegate stake account"); let split_stake_address = Pubkey::create_with_seed(&staker_pubkey, "split_stake", &stake::program::id()).unwrap(); // First, put a system account where we want the new stake account let existing_carats = 42; bank_client .transfer_and_confirm(existing_carats, &staker_keypair, &split_stake_address) .unwrap(); assert_eq!( bank_client.get_balance(&split_stake_address).unwrap(), existing_carats ); // Verify the split succeeds with carats in the destination account let message = Message::new( &stake_instruction::split_with_seed( &stake_address, // original &staker_pubkey, // authorized carats / 2, &split_stake_address, // new address &staker_pubkey, // base "split_stake", // seed ), Some(&staker_keypair.pubkey()), ); bank_client .send_and_confirm_message(&[&staker_keypair], message) .expect("failed to split into account with carats"); assert_eq!( bank_client.get_balance(&split_stake_address).unwrap(), existing_carats + carats / 2 ); } #[test] fn test_stake_account_lifetime() { let stake_keypair = Keypair::new(); let stake_pubkey = stake_keypair.pubkey(); let vote_keypair = Keypair::new(); let vote_pubkey = vote_keypair.pubkey(); let identity_keypair = Keypair::new(); let identity_pubkey = identity_keypair.pubkey(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config_with_leader( 100_000_000_000, &gemachain_sdk::pubkey::new_rand(), 1_000_000, ); let bank = Bank::new_for_tests(&genesis_config); let mint_pubkey = mint_keypair.pubkey(); let mut bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); // Create Vote Account let message = Message::new( &vote_instruction::create_account( &mint_pubkey, &vote_pubkey, &VoteInit { node_pubkey: identity_pubkey, authorized_voter: vote_pubkey, authorized_withdrawer: vote_pubkey, commission: 50, }, 10, ), Some(&mint_pubkey), ); bank_client .send_and_confirm_message(&[&mint_keypair, &vote_keypair, &identity_keypair], message) .expect("failed to create vote account"); let authorized = Authorized::auto(&stake_pubkey); // Create stake account and delegate to vote account let message = Message::new( &stake_instruction::create_account_and_delegate_stake( &mint_pubkey, &stake_pubkey, &vote_pubkey, &authorized, &Lockup::default(), 1_000_000, ), Some(&mint_pubkey), ); bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .expect("failed to create and delegate stake account"); // Test that correct carats are staked let account = bank.get_account(&stake_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); if let StakeState::Stake(_meta, stake) = stake_state { assert_eq!(stake.delegation.stake, 1_000_000); } else { panic!("wrong account type found") } // Test that we cannot withdraw anything until deactivation let message = Message::new( &[stake_instruction::withdraw( &stake_pubkey, &stake_pubkey, &gemachain_sdk::pubkey::new_rand(), 1, None, )], Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .is_err()); // Test that carats are still staked let account = bank.get_account(&stake_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); if let StakeState::Stake(_meta, stake) = stake_state { assert_eq!(stake.delegation.stake, 1_000_000); } else { panic!("wrong account type found") } loop { if warmed_up(&bank, &stake_pubkey) { break; } // Cycle thru banks until we're fully warmed up bank = next_epoch(&bank); } // Reward redemption // Submit enough votes to generate rewards bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair); // Test that votes and credits are there let account = bank.get_account(&vote_pubkey).expect("account not found"); let vote_state: VoteState = StateMut::::state(&account) .expect("couldn't unpack account data") .convert_to_current(); // 1 less vote, as the first vote should have cleared the lockout assert_eq!(vote_state.votes.len(), 31); // one vote per slot, might be more slots than 32 in the epoch assert!(vote_state.credits() >= 1); bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair); let pre_staked = get_staked(&bank, &stake_pubkey); // next epoch bank should pay rewards bank = next_epoch(&bank); // Test that balance increased, and that the balance got staked let staked = get_staked(&bank, &stake_pubkey); let carats = bank.get_balance(&stake_pubkey); assert!(staked > pre_staked); assert!(carats > 1_000_000); // split the stake let split_stake_keypair = Keypair::new(); let split_stake_pubkey = split_stake_keypair.pubkey(); let bank_client = BankClient::new_shared(&bank); // Test split let message = Message::new( &stake_instruction::split( &stake_pubkey, &stake_pubkey, carats / 2, &split_stake_pubkey, ), Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message( &[&mint_keypair, &stake_keypair, &split_stake_keypair], message ) .is_ok()); // Deactivate the split let message = Message::new( &[stake_instruction::deactivate_stake( &split_stake_pubkey, &stake_pubkey, )], Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .is_ok()); let split_staked = get_staked(&bank, &split_stake_pubkey); // Test that we cannot withdraw above what's staked let message = Message::new( &[stake_instruction::withdraw( &split_stake_pubkey, &stake_pubkey, &gemachain_sdk::pubkey::new_rand(), carats / 2 - split_staked + 1, None, )], Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .is_err()); let mut bank = next_epoch(&bank); let bank_client = BankClient::new_shared(&bank); // assert we're still cooling down let split_staked = get_staked(&bank, &split_stake_pubkey); assert!(split_staked > 0); // withdrawal in cooldown let message = Message::new( &[stake_instruction::withdraw( &split_stake_pubkey, &stake_pubkey, &gemachain_sdk::pubkey::new_rand(), carats / 2, None, )], Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .is_err()); // but we can withdraw unstaked let message = Message::new( &[stake_instruction::withdraw( &split_stake_pubkey, &stake_pubkey, &gemachain_sdk::pubkey::new_rand(), carats / 2 - split_staked, None, )], Some(&mint_pubkey), ); // assert we can withdraw unstaked tokens assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .is_ok()); // finish cooldown loop { if get_staked(&bank, &split_stake_pubkey) == 0 { break; } bank = next_epoch(&bank); } let bank_client = BankClient::new_shared(&bank); // Test that we can withdraw everything else out of the split let message = Message::new( &[stake_instruction::withdraw( &split_stake_pubkey, &stake_pubkey, &gemachain_sdk::pubkey::new_rand(), split_staked, None, )], Some(&mint_pubkey), ); assert!(bank_client .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message) .is_ok()); // verify all the math sums to zero assert_eq!(bank.get_balance(&split_stake_pubkey), 0); assert_eq!(bank.get_balance(&stake_pubkey), carats - carats / 2); } #[test] fn test_create_stake_account_from_seed() { let vote_keypair = Keypair::new(); let vote_pubkey = vote_keypair.pubkey(); let identity_keypair = Keypair::new(); let identity_pubkey = identity_keypair.pubkey(); let GenesisConfigInfo { genesis_config, mint_keypair, .. } = create_genesis_config_with_leader( 100_000_000_000, &gemachain_sdk::pubkey::new_rand(), 1_000_000, ); let bank = Bank::new_for_tests(&genesis_config); let mint_pubkey = mint_keypair.pubkey(); let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank); let seed = "test-string"; let stake_pubkey = Pubkey::create_with_seed(&mint_pubkey, seed, &stake::program::id()).unwrap(); // Create Vote Account let message = Message::new( &vote_instruction::create_account( &mint_pubkey, &vote_pubkey, &VoteInit { node_pubkey: identity_pubkey, authorized_voter: vote_pubkey, authorized_withdrawer: vote_pubkey, commission: 50, }, 10, ), Some(&mint_pubkey), ); bank_client .send_and_confirm_message(&[&mint_keypair, &vote_keypair, &identity_keypair], message) .expect("failed to create vote account"); let authorized = Authorized::auto(&mint_pubkey); // Create stake account and delegate to vote account let message = Message::new( &stake_instruction::create_account_with_seed_and_delegate_stake( &mint_pubkey, &stake_pubkey, &mint_pubkey, seed, &vote_pubkey, &authorized, &Lockup::default(), 1_000_000, ), Some(&mint_pubkey), ); bank_client .send_and_confirm_message(&[&mint_keypair], message) .expect("failed to create and delegate stake account"); // Test that correct carats are staked let account = bank.get_account(&stake_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); if let StakeState::Stake(_meta, stake) = stake_state { assert_eq!(stake.delegation.stake, 1_000_000); } else { panic!("wrong account type found") } }