diff --git a/programs/rewards/src/instruction.rs b/programs/rewards/src/instruction.rs index 0d07409c..2fbc1cc5 100644 --- a/programs/rewards/src/instruction.rs +++ b/programs/rewards/src/instruction.rs @@ -128,6 +128,18 @@ pub enum RewardsInstruction { #[account(3, signer, name = "deposit_authority")] #[account(4, writable, name = "reward_pool", desc = "The address of the reward pool")] CloseMining, + + /// Changes delegate mining account + #[account(0, writable, name = "reward_pool", desc = "The address of the reward pool")] + #[account(1, writable, name = "mining", desc = "The address of the mining account which belongs to the user and stores info about user's rewards")] + #[account(2, signer, name = "deposit_authority", desc = "The address of the Staking program's Registrar, which is PDA and is responsible for signing CPIs")] + #[account(3, signer, name = "mining_owner", desc = "The end user the mining accounts belongs to")] + #[account(4, writable, name = "old_delegate_mining", desc = "The address of the old delegate mining account")] + #[account(5, writable, name = "new_delegate_mining", desc = "The address of the new delegate mining account")] + ChangeDelegate { + /// Amount of staked tokens + staked_amount: u64, + }, } /// Creates 'InitializePool' instruction. @@ -376,3 +388,31 @@ pub fn close_mining( Instruction::new_with_borsh(*program_id, &RewardsInstruction::CloseMining, accounts) } + +/// Creates 'Distribute Rewards" instruction. +#[allow(clippy::too_many_arguments)] +pub fn change_delegate( + program_id: &Pubkey, + reward_pool: &Pubkey, + mining: &Pubkey, + deposit_authority: &Pubkey, + mining_owner: &Pubkey, + old_delegate_mining: &Pubkey, + new_delegate_mining: &Pubkey, + staked_amount: u64, +) -> Instruction { + let accounts = vec![ + AccountMeta::new(*reward_pool, false), + AccountMeta::new(*mining, false), + AccountMeta::new_readonly(*deposit_authority, true), + AccountMeta::new_readonly(*mining_owner, true), + AccountMeta::new(*old_delegate_mining, false), + AccountMeta::new(*new_delegate_mining, false), + ]; + + Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::ChangeDelegate { staked_amount }, + accounts, + ) +} diff --git a/programs/rewards/src/instructions/change_delegate.rs b/programs/rewards/src/instructions/change_delegate.rs new file mode 100644 index 00000000..96a87cc2 --- /dev/null +++ b/programs/rewards/src/instructions/change_delegate.rs @@ -0,0 +1,88 @@ +use crate::{ + asserts::get_delegate_mining, + state::{Mining, RewardPool}, + utils::{assert_and_deserialize_pool_and_mining, AccountLoader}, +}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + program_pack::Pack, pubkey::Pubkey, +}; + +/// Instruction context +pub struct ChangeDelegateContext<'a, 'b> { + reward_pool: &'a AccountInfo<'b>, + mining: &'a AccountInfo<'b>, + deposit_authority: &'a AccountInfo<'b>, + mining_owner: &'a AccountInfo<'b>, + old_delegate_mining: &'a AccountInfo<'b>, + new_delegate_mining: &'a AccountInfo<'b>, +} + +impl<'a, 'b> ChangeDelegateContext<'a, 'b> { + /// New instruction context + pub fn new( + program_id: &Pubkey, + accounts: &'a [AccountInfo<'b>], + ) -> Result, ProgramError> { + let account_info_iter = &mut accounts.iter().enumerate(); + + let reward_pool = AccountLoader::next_with_owner(account_info_iter, program_id)?; + let mining = AccountLoader::next_with_owner(account_info_iter, program_id)?; + let deposit_authority = AccountLoader::next_signer(account_info_iter)?; + let mining_owner = AccountLoader::next_signer(account_info_iter)?; + let old_delegate_mining = AccountLoader::next_with_owner(account_info_iter, program_id)?; + let new_delegate_mining = AccountLoader::next_with_owner(account_info_iter, program_id)?; + + Ok(ChangeDelegateContext { + reward_pool, + mining, + deposit_authority, + mining_owner, + old_delegate_mining, + new_delegate_mining, + }) + } + + /// Process instruction + #[allow(clippy::too_many_arguments)] + pub fn process(&self, program_id: &Pubkey, staked_amount: u64) -> ProgramResult { + let (mut reward_pool, mut mining) = assert_and_deserialize_pool_and_mining( + program_id, + self.mining_owner.key, + self.reward_pool, + self.mining, + self.deposit_authority, + )?; + + // if new_delegate_mining.is_none that means that new_delegate == self + let mut new_delegate_mining = get_delegate_mining(self.new_delegate_mining, self.mining)?; + // if old_delegate_mining.is_none that means that old_delegate == self + let mut old_delegate_mining = get_delegate_mining(self.old_delegate_mining, self.mining)?; + + reward_pool.change_delegate( + &mut mining, + new_delegate_mining.as_mut(), + old_delegate_mining.as_mut(), + staked_amount, + )?; + + RewardPool::pack(reward_pool, *self.reward_pool.data.borrow_mut())?; + Mining::pack(mining, *self.mining.data.borrow_mut())?; + + if let Some(new_delegate_mining) = new_delegate_mining { + Mining::pack( + new_delegate_mining, + *self.new_delegate_mining.data.borrow_mut(), + )?; + } + + if let Some(old_delegate_mining) = old_delegate_mining { + Mining::pack( + old_delegate_mining, + *self.old_delegate_mining.data.borrow_mut(), + )?; + } + + Ok(()) + } +} diff --git a/programs/rewards/src/instructions/mod.rs b/programs/rewards/src/instructions/mod.rs index b08b1025..1e16ef09 100644 --- a/programs/rewards/src/instructions/mod.rs +++ b/programs/rewards/src/instructions/mod.rs @@ -1,5 +1,6 @@ //! Program instructions +mod change_delegate; mod claim; mod close_mining; mod deposit_mining; @@ -10,6 +11,7 @@ mod initialize_mining; mod initialize_pool; mod withdraw_mining; +pub use change_delegate::*; pub use claim::*; pub use close_mining::*; pub use deposit_mining::*; diff --git a/programs/rewards/src/processor.rs b/programs/rewards/src/processor.rs index e9ef6738..15eaf942 100644 --- a/programs/rewards/src/processor.rs +++ b/programs/rewards/src/processor.rs @@ -2,9 +2,9 @@ use crate::{ instruction::RewardsInstruction, instructions::{ - ClaimContext, CloseMiningContext, DepositMiningContext, DistributeRewardsContext, - ExtendStakeContext, FillVaultContext, InitializeMiningContext, InitializePoolContext, - WithdrawMiningContext, + ChangeDelegateContext, ClaimContext, CloseMiningContext, DepositMiningContext, + DistributeRewardsContext, ExtendStakeContext, FillVaultContext, InitializeMiningContext, + InitializePoolContext, WithdrawMiningContext, }, }; use borsh::BorshDeserialize; @@ -93,5 +93,9 @@ pub fn process_instruction( msg!("RewardsInstruction: CloseAccount"); CloseMiningContext::new(program_id, accounts)?.process() } + RewardsInstruction::ChangeDelegate { staked_amount } => { + msg!("RewardsInstruction: ChangeDelegate"); + ChangeDelegateContext::new(program_id, accounts)?.process(program_id, staked_amount) + } } } diff --git a/programs/rewards/src/state/reward_pool.rs b/programs/rewards/src/state/reward_pool.rs index 52f5464f..3fb8961a 100644 --- a/programs/rewards/src/state/reward_pool.rs +++ b/programs/rewards/src/state/reward_pool.rs @@ -259,6 +259,32 @@ impl RewardPool { Ok(()) } + + pub fn change_delegate( + &mut self, + mining: &mut Mining, + new_delegate_mining: Option<&mut Mining>, + old_delegate_mining: Option<&mut Mining>, + staked_amount: u64, + ) -> ProgramResult { + mining.refresh_rewards(&self.calculator)?; + + if let Some(old_delegate_mining) = old_delegate_mining { + old_delegate_mining.stake_from_others = old_delegate_mining + .stake_from_others + .safe_sub(staked_amount)?; + self.total_share = self.total_share.safe_sub(staked_amount)?; + } + + if let Some(new_delegate_mining) = new_delegate_mining { + new_delegate_mining.stake_from_others = new_delegate_mining + .stake_from_others + .safe_add(staked_amount)?; + self.total_share = self.total_share.safe_add(staked_amount)?; + } + + Ok(()) + } } impl Sealed for RewardPool {}