Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add detector for the forced unstake from the delinquent validator #76

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"unstakes",
"unstaking",
"unsync",
"upgrader",
"validatr"
]
}
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions programs/marinade-finance/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,10 @@ pub enum MarinadeError {

#[msg("Capacity of the list must be not less than it's current size")]
ShrinkingListWithDeletingContents, // 6086 0x17c6

#[msg("Upgrading invariant violation")]
UpgradingInvariantViolation, // 6087 0x17c7

#[msg("Delinquent upgrader is not done")]
DelinquentUpgraderIsNotDone, // 6088 0x17c8
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
require_lte,
state::{
fee::FeeCents, liq_pool::LiqPool, stake_system::StakeSystem,
validator_system::ValidatorSystem, Fee,
validator_system::ValidatorSystem, Fee, delinquent_upgrader::DelinquentUpgraderState,
},
State, ID,
};
Expand Down Expand Up @@ -188,6 +188,7 @@ impl<'info> Initialize<'info> {
last_stake_move_epoch: 0,
stake_moved: 0,
max_stake_moved_per_epoch: Fee::from_basis_points(10000), // 100% of total_lamports_under_control
delinquent_upgrader: DelinquentUpgraderState::Done,
});

emit!(InitializeEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
events::crank::{DeactivateStakeEvent, SplitStakeAccountInfo},
require_lt,
state::{
stake_system::{StakeList, StakeSystem},
stake_system::{StakeList, StakeSystem, StakeStatus},
validator_system::ValidatorList,
},
State,
Expand Down Expand Up @@ -93,10 +93,14 @@ impl<'info> DeactivateStake<'info> {
)?;
let last_update_stake_delegation = stake.last_update_delegated_lamports;

// check the account is not already in emergency_unstake
require!(self.state.delinquent_upgrader.is_done(), MarinadeError::DelinquentUpgraderIsNotDone);
require_eq!(
stake.is_emergency_unstaking,
0,
stake.last_update_status, StakeStatus::Active,
MarinadeError::RequiredActiveStake
);
// check the account is not already in emergency_unstake
require!(
!stake.is_emergency_unstaking,
MarinadeError::StakeAccountIsEmergencyUnstaking
);

Expand Down Expand Up @@ -191,6 +195,8 @@ impl<'info> DeactivateStake<'info> {
]],
))?;

stake.last_update_status = StakeStatus::Deactivating;

// Return back the rent reserve of unused split stake account
self.return_unused_split_stake_account_rent()?;

Expand Down Expand Up @@ -234,7 +240,8 @@ impl<'info> DeactivateStake<'info> {
&self.split_stake_account.key(),
split_amount,
&self.clock,
0, // is_emergency_unstaking? no
false, // is_emergency_unstaking? no
false, // is_active? no
)?;

let split_instruction = stake::instruction::split(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anchor_lang::prelude::*;

use crate::{
error::MarinadeError,
state::{delinquent_upgrader::DelinquentUpgraderState, validator_system::ValidatorList},
State,
};

#[derive(Accounts)]
pub struct FinalizeDelinquentUpgrade<'info> {
#[account(mut)]
pub state: Account<'info, State>,
#[account(
mut,
address = state.validator_system.validator_list.account,
)]
pub validator_list: Account<'info, ValidatorList>,
}

impl<'info> FinalizeDelinquentUpgrade<'info> {
pub fn process(&mut self, mut max_validators: u32) -> Result<()> {
require!(!self.state.paused, MarinadeError::ProgramIsPaused);

let (visited_count, delinquent_balance_left) =
if let DelinquentUpgraderState::IteratingValidators {
mut visited_count,
mut delinquent_balance_left,
} = &self.state.delinquent_upgrader
{
while visited_count < self.state.validator_system.validator_count()
&& max_validators > 0
{
let mut validator = self.state.validator_system.get(
&self.validator_list.to_account_info().data.as_ref().borrow(),
visited_count,
)?;
delinquent_balance_left -=
validator.active_balance - validator.delinquent_upgrader_active_balance;
validator.active_balance = validator.delinquent_upgrader_active_balance;
validator.delinquent_upgrader_active_balance = 0;
self.state.validator_system.set(
&mut self
.validator_list
.to_account_info()
.data
.as_ref()
.borrow_mut(),
visited_count,
validator,
)?;
visited_count += 1;
max_validators -= 1;
}
(visited_count, delinquent_balance_left)
} else {
return err!(MarinadeError::UpgradingInvariantViolation);
};

if visited_count == self.state.validator_system.validator_count() {
require_eq!(
delinquent_balance_left,
0,
MarinadeError::UpgradingInvariantViolation
);
self.state.delinquent_upgrader = DelinquentUpgraderState::Done;
} else {
self.state.delinquent_upgrader = DelinquentUpgraderState::IteratingValidators {
visited_count,
delinquent_balance_left,
};
}
Ok(())
}
}
17 changes: 16 additions & 1 deletion programs/marinade-finance/src/instructions/crank/merge_stakes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anchor_lang::solana_program::{program::invoke_signed, stake};
use anchor_spl::stake::{withdraw, Stake, StakeAccount, Withdraw};

use crate::events::crank::MergeStakesEvent;
use crate::state::stake_system::StakeList;
use crate::state::stake_system::{StakeList, StakeStatus};
use crate::state::validator_system::ValidatorList;
use crate::{error::MarinadeError, state::stake_system::StakeSystem, State};

Expand Down Expand Up @@ -67,6 +67,10 @@ impl<'info> MergeStakes<'info> {
validator_index: u32,
) -> Result<()> {
require!(!self.state.paused, MarinadeError::ProgramIsPaused);
require!(
self.state.delinquent_upgrader.is_done(),
MarinadeError::DelinquentUpgraderIsNotDone
);

let mut validator = self.state.validator_system.get(
&self.validator_list.to_account_info().data.as_ref().borrow(),
Expand All @@ -91,6 +95,11 @@ impl<'info> MergeStakes<'info> {
return err!(MarinadeError::DestinationStakeMustBeDelegated)
.map_err(|e| e.with_account_name("destination_stake"));
};
require_eq!(
destination_stake_info.last_update_status,
StakeStatus::Active,
MarinadeError::DestinationStakeMustNotBeDeactivating
);
require_eq!(
destination_delegation.deactivation_epoch,
std::u64::MAX,
Expand Down Expand Up @@ -120,6 +129,12 @@ impl<'info> MergeStakes<'info> {
return err!(MarinadeError::SourceStakeMustBeDelegated)
.map_err(|e| e.with_account_name("source_stake"));
};

require_eq!(
source_stake_info.last_update_status,
StakeStatus::Active,
MarinadeError::SourceStakeMustNotBeDeactivating
);
require_eq!(
source_delegation.deactivation_epoch,
std::u64::MAX,
Expand Down
2 changes: 2 additions & 0 deletions programs/marinade-finance/src/instructions/crank/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod deactivate_stake;
pub mod finalize_delinquent_upgrade;
pub mod merge_stakes;
pub mod redelegate;
pub mod stake_reserve;
pub mod update;

pub use deactivate_stake::*;
pub use finalize_delinquent_upgrade::*;
pub use merge_stakes::*;
pub use redelegate::*;
pub use stake_reserve::*;
Expand Down
20 changes: 13 additions & 7 deletions programs/marinade-finance/src/instructions/crank/redelegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
error::MarinadeError,
events::crank::{RedelegateEvent, SplitStakeAccountInfo},
state::{
stake_system::{StakeList, StakeRecord, StakeSystem},
stake_system::{StakeList, StakeRecord, StakeSystem, StakeStatus},
validator_system::ValidatorList,
},
State,
Expand Down Expand Up @@ -132,10 +132,13 @@ impl<'info> ReDelegate<'info> {
)?;
let last_update_delegation = stake.last_update_delegated_lamports;

// check the account is not already in emergency_unstake
require_eq!(
stake.is_emergency_unstaking,
0,
stake.last_update_status, StakeStatus::Active,
MarinadeError::RequiredActiveStake
);
// check the account is not already in emergency_unstake
require!(
!stake.is_emergency_unstaking,
MarinadeError::StakeAccountIsEmergencyUnstaking
);

Expand Down Expand Up @@ -244,13 +247,14 @@ impl<'info> ReDelegate<'info> {
self.return_rent_unused_stake_account(self.split_stake_account.to_account_info())?;

// TODO: deprecate "is_emergency_unstaking"
stake.is_emergency_unstaking = 0;
stake.is_emergency_unstaking = false;
// all lamports will be moved to the re-delegated account
let amount_to_redelegate_whole_account = stake.last_update_delegated_lamports;
// this account will enter redelegate-deactivating mode, all lamports will be sent to the other account
// so we set last_update_delegated_lamports = 0 because all lamports are gone
// after completing deactivation, whatever is there minus rent is considered last rewards for the account
stake.last_update_delegated_lamports = 0;
stake.last_update_status = StakeStatus::Deactivating;

// account to redelegate is the whole source account
(
Expand Down Expand Up @@ -306,7 +310,8 @@ impl<'info> ReDelegate<'info> {
&self.redelegate_stake_account.key(),
redelegate_amount_effective,
&self.clock,
0, // is_emergency_unstaking
false, // is_emergency_unstaking
true, // is_active
)?;

// we now consider amount no longer "active" for this specific validator
Expand Down Expand Up @@ -417,7 +422,8 @@ impl<'info> ReDelegate<'info> {
// After completing deactivation, whatever is there minus rent is considered last rewards for the account
&self.clock,
// TODO: deprecate "is_emergency_unstaking"
0,
false,
false, // is_active
)?;

// split stake account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ impl<'info> StakeReserve<'info> {
/// pub fn stake_reserve()
pub fn process(&mut self, validator_index: u32) -> Result<()> {
require!(!self.state.paused, MarinadeError::ProgramIsPaused);

sol_log_compute_units();
require!(self.state.delinquent_upgrader.is_done(), MarinadeError::DelinquentUpgraderIsNotDone);

// record for event
let total_active_balance = self.state.validator_system.total_active_balance;
Expand Down Expand Up @@ -279,7 +278,8 @@ impl<'info> StakeReserve<'info> {
&self.stake_account.key(),
stake_target,
&self.clock,
0, // is_emergency_unstaking? no
false, // is_emergency_unstaking? no
true, // is_active? yes
)?;

// update validator record and store in list
Expand Down
Loading