From 23d88f4d953a8433ae82b9bf6704cd5fa598d203 Mon Sep 17 00:00:00 2001 From: Keyvan Khademi Date: Thu, 29 Aug 2024 08:30:35 -0700 Subject: [PATCH] feat: deprecate unlocking duration (#508) * feat: deprecate unlocking duration * fix: remove unlocking duration from sdk, tests, and wasm * chore: Remove deprecated unlocking duration field --- staking/app/StakeConnection.ts | 24 +--- .../src/staking/instructions.rs | 7 +- staking/integration-tests/tests/stability.rs | 7 +- staking/programs/integrity-pool/src/lib.rs | 8 +- .../programs/integrity-pool/src/state/pool.rs | 8 +- .../integrity-pool/src/utils/clock.rs | 1 - staking/programs/staking/src/lib.rs | 15 +- .../staking/src/state/global_config.rs | 22 +-- .../programs/staking/src/state/positions.rs | 133 ++++++------------ staking/programs/staking/src/utils/clock.rs | 2 + staking/programs/staking/src/utils/risk.rs | 95 ++++++------- .../staking/src/utils/voter_weight.rs | 17 ++- staking/programs/staking/src/wasm.rs | 14 +- staking/target/idl/staking.json | 2 +- staking/target/types/staking.ts | 2 +- staking/tests/config.ts | 24 ++-- 16 files changed, 140 insertions(+), 241 deletions(-) diff --git a/staking/app/StakeConnection.ts b/staking/app/StakeConnection.ts index 6c0cc8dd3..ec63ed89b 100644 --- a/staking/app/StakeConnection.ts +++ b/staking/app/StakeConnection.ts @@ -418,8 +418,7 @@ export class StakeConnection { [wasm.PositionState.LOCKED, wasm.PositionState.LOCKING].includes( stakeAccount.stakeAccountPositionsWasm.getPositionState( el.index, - BigInt(currentEpoch.toString()), - this.config.unlockingDuration + BigInt(currentEpoch.toString()) ) ) ) @@ -1060,13 +1059,10 @@ export class StakeConnection { const time = new BN(Date.now() / 1000); const currentEpoch = time.div(this.config.epochDuration); - const unlockingDuration = this.config.unlockingDuration; const currentEpochBI = BigInt(currentEpoch.toString()); - const lockedBalanceSummary = positionAccountWasm.getLockedBalanceSummary( - currentEpochBI, - unlockingDuration - ); + const lockedBalanceSummary = + positionAccountWasm.getLockedBalanceSummary(currentEpochBI); const epochOfFirstStake: BN = positionAccountJs.positions.reduce( (prev: BN | undefined, curr) => { @@ -1250,15 +1246,11 @@ export class StakeAccount { ); const currentEpoch = unixTime.div(this.config.epochDuration); - const unlockingDuration = this.config.unlockingDuration; const currentEpochBI = BigInt(currentEpoch.toString()); const unvestedBN = new BN(unvestedBalance.toString()); const lockedSummaryBI = - this.stakeAccountPositionsWasm.getLockedBalanceSummary( - currentEpochBI, - unlockingDuration - ); + this.stakeAccountPositionsWasm.getLockedBalanceSummary(currentEpochBI); let lockingBN = new BN(lockedSummaryBI.locking.toString()); let lockedBN = new BN(lockedSummaryBI.locked.toString()); @@ -1372,11 +1364,9 @@ export class StakeAccount { public getVoterWeight(unixTime: BN): PythBalance { let currentEpoch = unixTime.div(this.config.epochDuration); - let unlockingDuration = this.config.unlockingDuration; const voterWeightBI = this.stakeAccountPositionsWasm.getVoterWeight( BigInt(currentEpoch.toString()), - unlockingDuration, BigInt( this.votingAccountMetadataWasm.getCurrentAmountLocked( BigInt(currentEpoch.toString()) @@ -1430,11 +1420,7 @@ export class StakeAccount { } private addUnlockingPeriod(unixTime: BN) { - return unixTime.add( - this.config.epochDuration.mul( - new BN(this.config.unlockingDuration).add(new BN(1)) - ) - ); + return unixTime.add(this.config.epochDuration.mul(new BN(2))); } public getNetExcessGovernanceAtVesting(unixTime: BN): BN { diff --git a/staking/integration-tests/src/staking/instructions.rs b/staking/integration-tests/src/staking/instructions.rs index 0154ddf5f..fe447e829 100644 --- a/staking/integration-tests/src/staking/instructions.rs +++ b/staking/integration-tests/src/staking/instructions.rs @@ -26,10 +26,7 @@ use { }, anchor_spl::token::spl_token, integrity_pool::utils::{ - clock::{ - EPOCH_DURATION, - UNLOCKING_DURATION, - }, + clock::EPOCH_DURATION, types::frac64, }, litesvm::types::TransactionResult, @@ -59,7 +56,7 @@ pub fn init_config_account(svm: &mut litesvm::LiteSVM, payer: &Keypair, pyth_tok governance_authority: payer.pubkey(), pyth_token_mint, pyth_governance_realm: MAINNET_REALM_ID, - unlocking_duration: UNLOCKING_DURATION, + removed_unlocking_duration: 1, epoch_duration: EPOCH_DURATION, freeze: false, pda_authority: payer.pubkey(), diff --git a/staking/integration-tests/tests/stability.rs b/staking/integration-tests/tests/stability.rs index 0010e1b8a..69027c76c 100644 --- a/staking/integration-tests/tests/stability.rs +++ b/staking/integration-tests/tests/stability.rs @@ -45,7 +45,6 @@ use { integrity_pool::{ state::pool::PoolData, utils::{ - clock::UNLOCKING_DURATION, constants::MAX_PUBLISHERS, types::FRAC_64_MULTIPLIER, }, @@ -334,7 +333,7 @@ fn sanity_check_publisher( for i in 0..positions.get_position_capacity() { if let Some(position) = positions.read_position(i).unwrap() { let position_state = position - .get_current_position(get_current_epoch(svm), UNLOCKING_DURATION) + .get_current_position(get_current_epoch(svm)) .unwrap(); if matches!(position, Position { target_with_parameters: TargetWithParameters::IntegrityPool { publisher: p, .. }, @@ -518,7 +517,7 @@ fn test_stability(props: StabilityTestProps) { for i in 0..positions.get_position_capacity() { if let Some(position) = positions.read_position(i).unwrap() { let position_state = position - .get_current_position(get_current_epoch(&mut svm), UNLOCKING_DURATION) + .get_current_position(get_current_epoch(&mut svm)) .unwrap(); if matches!(position, Position { target_with_parameters: TargetWithParameters::IntegrityPool { publisher: p, .. }, @@ -682,7 +681,7 @@ fn test_stability(props: StabilityTestProps) { for i in 0..positions.get_position_capacity() { if let Some(position) = positions.read_position(i).unwrap() { let position_state = position - .get_current_position(get_current_epoch(&mut svm), UNLOCKING_DURATION) + .get_current_position(get_current_epoch(&mut svm)) .unwrap(); if matches!( position, diff --git a/staking/programs/integrity-pool/src/lib.rs b/staking/programs/integrity-pool/src/lib.rs index 04a5fac10..61c730479 100644 --- a/staking/programs/integrity-pool/src/lib.rs +++ b/staking/programs/integrity-pool/src/lib.rs @@ -7,10 +7,7 @@ use { TargetWithParameters, }, utils::{ - clock::{ - get_current_epoch, - UNLOCKING_DURATION, - }, + clock::get_current_epoch, constants::{ MAX_PUBLISHERS, POOL_CONFIG, @@ -177,8 +174,7 @@ pub mod integrity_pool { .read_position(position_index.into())? .ok_or(IntegrityPoolError::ThisCodeShouldBeUnreachable)?; - let position_state = - position.get_current_position(get_current_epoch()?, UNLOCKING_DURATION)?; + let position_state = position.get_current_position(get_current_epoch()?)?; pool_data.remove_delegation( publisher.key, &ctx.accounts.stake_account_positions.key(), diff --git a/staking/programs/integrity-pool/src/state/pool.rs b/staking/programs/integrity-pool/src/state/pool.rs index 45a92d158..5eabf0216 100644 --- a/staking/programs/integrity-pool/src/state/pool.rs +++ b/staking/programs/integrity-pool/src/state/pool.rs @@ -6,10 +6,7 @@ use { crate::{ error::IntegrityPoolError, utils::{ - clock::{ - time_to_epoch, - UNLOCKING_DURATION, - }, + clock::time_to_epoch, constants::{ MAX_EVENTS, MAX_PUBLISHERS, @@ -142,8 +139,7 @@ impl PoolData { break; } - let position_state = - position.get_current_position(event.epoch, UNLOCKING_DURATION)?; + let position_state = position.get_current_position(event.epoch)?; match position_state { PositionState::LOCKED | PositionState::PREUNLOCKING => {} diff --git a/staking/programs/integrity-pool/src/utils/clock.rs b/staking/programs/integrity-pool/src/utils/clock.rs index 06a9af158..b821bc9c9 100644 --- a/staking/programs/integrity-pool/src/utils/clock.rs +++ b/staking/programs/integrity-pool/src/utils/clock.rs @@ -7,7 +7,6 @@ use { }; pub const EPOCH_DURATION: u64 = 60 * 60 * 24 * 7; // 1 week -pub const UNLOCKING_DURATION: u8 = 1; // 1 epoch /// Computes Pyth clock. /// Right now it's just the current Unix timestamp divided by the epoch duration. diff --git a/staking/programs/staking/src/lib.rs b/staking/programs/staking/src/lib.rs index c2eca4f56..da202af9b 100644 --- a/staking/programs/staking/src/lib.rs +++ b/staking/programs/staking/src/lib.rs @@ -52,14 +52,12 @@ pub mod staking { /// Creates a global config for the program use super::*; - pub fn init_config(ctx: Context, global_config: GlobalConfig) -> Result<()> { let config_account = &mut ctx.accounts.config_account; config_account.bump = ctx.bumps.config_account; config_account.governance_authority = global_config.governance_authority; config_account.pyth_token_mint = global_config.pyth_token_mint; config_account.pyth_governance_realm = global_config.pyth_governance_realm; - config_account.unlocking_duration = global_config.unlocking_duration; config_account.epoch_duration = global_config.epoch_duration; config_account.freeze = global_config.freeze; config_account.pda_authority = global_config.pda_authority; @@ -227,7 +225,6 @@ pub mod staking { stake_account_custody.amount, unvested_balance, current_epoch, - config.unlocking_duration, )?; if let Some(target_account) = maybe_target_account { @@ -262,7 +259,6 @@ pub mod staking { stake_account_positions.merge_target_positions( current_epoch, - config.unlocking_duration, &mut stake_account_metadata.next_index, target_with_parameters, )?; @@ -322,7 +318,7 @@ pub mod staking { .checked_sub(amount) .ok_or_else(|| error!(ErrorCode::AmountBiggerThanPosition))?; - match current_position.get_current_position(current_epoch, config.unlocking_duration)? { + match current_position.get_current_position(current_epoch)? { PositionState::LOCKED => { // If remaining amount is 0 keep only 1 position if remaining_amount == 0 { @@ -434,7 +430,6 @@ pub mod staking { remaining_balance, unvested_balance, current_epoch, - config.unlocking_duration, ) .is_err() { @@ -457,7 +452,6 @@ pub mod staking { ctx.accounts.stake_account_custody.amount, unvested_balance, current_epoch, - config.unlocking_duration, ) .is_err() { @@ -500,7 +494,6 @@ pub mod staking { stake_account_custody.amount, unvested_balance, current_epoch, - config.unlocking_duration, )?; let epoch_of_snapshot: u64; @@ -573,7 +566,6 @@ pub mod staking { voter_record.voter_weight = compute_voter_weight( stake_account_positions, epoch_of_snapshot, - config.unlocking_duration, governance_target.get_current_amount_locked(epoch_of_snapshot)?, MAX_VOTER_WEIGHT, )?; @@ -688,7 +680,6 @@ pub mod staking { config.pyth_token_list_time, )?, current_epoch, - config.unlocking_duration, )?; // Check that there aren't any positions (i.e., staked tokens) in the source account. @@ -743,7 +734,6 @@ pub mod staking { config.pyth_token_list_time, )?, current_epoch, - config.unlocking_duration, )?; utils::risk::validate( @@ -757,7 +747,6 @@ pub mod staking { config.pyth_token_list_time, )?, current_epoch, - config.unlocking_duration, )?; // Delete current request @@ -817,7 +806,6 @@ pub mod staking { let next_index = &mut ctx.accounts.stake_account_metadata.next_index; let current_epoch = get_current_epoch(&ctx.accounts.config)?; - let unlocking_duration = ctx.accounts.config.unlocking_duration; let SlashedAmounts { total_slashed, @@ -825,7 +813,6 @@ pub mod staking { preunlocking_slashed, } = stake_account_positions.slash_positions( current_epoch, - unlocking_duration, next_index, ctx.accounts.stake_account_custody.amount, publisher.key, diff --git a/staking/programs/staking/src/state/global_config.rs b/staking/programs/staking/src/state/global_config.rs index daba037aa..9c59b6b49 100644 --- a/staking/programs/staking/src/state/global_config.rs +++ b/staking/programs/staking/src/state/global_config.rs @@ -6,16 +6,18 @@ use { #[account] #[derive(Default, BorshSchema)] pub struct GlobalConfig { - pub bump: u8, - pub governance_authority: Pubkey, - pub pyth_token_mint: Pubkey, - pub pyth_governance_realm: Pubkey, - pub unlocking_duration: u8, - pub epoch_duration: u64, // epoch duration in seconds - pub freeze: bool, - pub pda_authority: Pubkey, /* Authority that can authorize the transfer of locked - * tokens */ - pub governance_program: Pubkey, // Governance program id + pub bump: u8, + pub governance_authority: Pubkey, + pub pyth_token_mint: Pubkey, + pub pyth_governance_realm: Pubkey, + // unlocking_duration is deprecated, but we need to keep the space for account structure + pub removed_unlocking_duration: u8, + pub epoch_duration: u64, // epoch duration in seconds + pub freeze: bool, + pub pda_authority: Pubkey, /* Authority that can authorize the transfer of + * locked + * tokens */ + pub governance_program: Pubkey, // Governance program id /// Once the pyth token is listed, governance can update the config to set this value. /// Once this value is set, vesting schedules that depend on the token list date can start diff --git a/staking/programs/staking/src/state/positions.rs b/staking/programs/staking/src/state/positions.rs index 284b2d5cb..92fa7209c 100644 --- a/staking/programs/staking/src/state/positions.rs +++ b/staking/programs/staking/src/state/positions.rs @@ -1,6 +1,9 @@ use { super::target::TargetMetadata, - crate::error::ErrorCode, + crate::{ + error::ErrorCode, + utils::clock::UNLOCKING_DURATION, + }, anchor_lang::{ prelude::{ borsh::BorshSchema, @@ -209,18 +212,12 @@ impl<'a> DynamicPositionArray<'a> { Ok(false) } - pub fn get_target_exposure( - &self, - target: &Target, - current_epoch: u64, - unlocking_duration: u8, - ) -> Result { + pub fn get_target_exposure(&self, target: &Target, current_epoch: u64) -> Result { let mut exposure: u64 = 0; for i in 0..self.get_position_capacity() { if let Some(position) = self.read_position(i)? { if position.target_with_parameters.get_target() == *target - && position.get_current_position(current_epoch, unlocking_duration)? - != PositionState::UNLOCKED + && position.get_current_position(current_epoch)? != PositionState::UNLOCKED { exposure = exposure .checked_add(position.amount) @@ -244,7 +241,6 @@ impl<'a> DynamicPositionArray<'a> { pub fn merge_target_positions( &mut self, current_epoch: u64, - unlocking_duration: u8, next_index: &mut u8, target_with_parameters: TargetWithParameters, ) -> Result<()> { @@ -253,18 +249,12 @@ impl<'a> DynamicPositionArray<'a> { i -= 1; if let Some(position) = self.read_position(i)? { if position.target_with_parameters == target_with_parameters { - if position.get_current_position(current_epoch, unlocking_duration)? - == PositionState::UNLOCKED - { + if position.get_current_position(current_epoch)? == PositionState::UNLOCKED { self.make_none(i, next_index)?; } else { for j in 0..i { if let Some(mut other_position) = self.read_position(j)? { - if position.is_equivalent( - &other_position, - current_epoch, - unlocking_duration, - ) { + if position.is_equivalent(&other_position, current_epoch) { self.make_none(i, next_index)?; other_position.amount += position.amount; other_position.activation_epoch = std::cmp::min( @@ -287,7 +277,6 @@ impl<'a> DynamicPositionArray<'a> { pub fn slash_positions( &mut self, current_epoch: u64, - unlocking_duration: u8, next_index: &mut u8, custody_account_amount: u64, publisher: &Pubkey, @@ -306,10 +295,8 @@ impl<'a> DynamicPositionArray<'a> { let position = self.read_position(i)?; if let Some(position_data) = position { - let prev_state = - position_data.get_current_position(current_epoch - 1, unlocking_duration)?; - let current_state = - position_data.get_current_position(current_epoch, unlocking_duration)?; + let prev_state = position_data.get_current_position(current_epoch - 1)?; + let current_state = position_data.get_current_position(current_epoch)?; if matches!( position_data.target_with_parameters, TargetWithParameters::IntegrityPool { publisher: publisher_pubkey } if publisher_pubkey == *publisher, @@ -356,8 +343,7 @@ impl<'a> DynamicPositionArray<'a> { i += 1; } - let governance_exposure = - self.get_target_exposure(&Target::Voting, current_epoch, unlocking_duration)?; + let governance_exposure = self.get_target_exposure(&Target::Voting, current_epoch)?; let total_slashed = locked_slashed + unlocking_slashed + preunlocking_slashed; if let Some(mut remaining) = @@ -366,10 +352,8 @@ impl<'a> DynamicPositionArray<'a> { let mut i = 0; while i < usize::from(*next_index) && remaining > 0 { if let Some(position) = self.read_position(i)? { - let prev_state = - position.get_current_position(current_epoch - 1, unlocking_duration)?; - let current_state = - position.get_current_position(current_epoch, unlocking_duration)?; + let prev_state = position.get_current_position(current_epoch - 1)?; + let current_state = position.get_current_position(current_epoch)?; if position.target_with_parameters == TargetWithParameters::Voting && current_state != PositionState::UNLOCKED @@ -557,11 +541,7 @@ impl Position { /// next epoch boundary. In order to get the actual current state, we need the current /// epoch. This encapsulates that logic so that other parts of the code can use the actual /// state. - pub fn get_current_position( - &self, - current_epoch: u64, - unlocking_duration: u8, - ) -> Result { + pub fn get_current_position(&self, current_epoch: u64) -> Result { if current_epoch < self.activation_epoch { Ok(PositionState::LOCKING) } else { @@ -570,8 +550,7 @@ impl Position { Some(unlocking_start) => { let has_activated: bool = self.activation_epoch <= current_epoch; let unlock_started: bool = unlocking_start <= current_epoch; - let unlock_ended: bool = - unlocking_start + u64::from(unlocking_duration) <= current_epoch; + let unlock_ended: bool = unlocking_start + UNLOCKING_DURATION <= current_epoch; if has_activated && !unlock_started { Ok(PositionState::PREUNLOCKING) @@ -592,16 +571,10 @@ impl Position { * pool, therefore `pool_authority` should ensure rewards have been claimed before * allowing merging positions. */ - pub fn is_equivalent( - &self, - other: &Position, - current_epoch: u64, - unlocking_duration: u8, - ) -> bool { - self.get_current_position(current_epoch, unlocking_duration) - == other.get_current_position(current_epoch, unlocking_duration) - && self.get_current_position(current_epoch.saturating_sub(1), unlocking_duration) - == other.get_current_position(current_epoch.saturating_sub(1), unlocking_duration) + pub fn is_equivalent(&self, other: &Position, current_epoch: u64) -> bool { + self.get_current_position(current_epoch) == other.get_current_position(current_epoch) + && self.get_current_position(current_epoch.saturating_sub(1)) + == other.get_current_position(current_epoch.saturating_sub(1)) && self.target_with_parameters == other.target_with_parameters } @@ -669,30 +642,21 @@ pub mod tests { target_with_parameters: TargetWithParameters::Voting, amount: 10, }; - assert_eq!( - PositionState::LOCKING, - p.get_current_position(0, 2).unwrap() - ); - assert_eq!( - PositionState::LOCKING, - p.get_current_position(7, 2).unwrap() - ); + assert_eq!(PositionState::LOCKING, p.get_current_position(0).unwrap()); + assert_eq!(PositionState::LOCKING, p.get_current_position(7).unwrap()); assert_eq!( PositionState::PREUNLOCKING, - p.get_current_position(8, 2).unwrap() + p.get_current_position(8).unwrap() ); assert_eq!( PositionState::PREUNLOCKING, - p.get_current_position(11, 2).unwrap() + p.get_current_position(11).unwrap() ); assert_eq!( PositionState::UNLOCKING, - p.get_current_position(13, 2).unwrap() - ); - assert_eq!( - PositionState::UNLOCKED, - p.get_current_position(14, 2).unwrap() + p.get_current_position(12).unwrap() ); + assert_eq!(PositionState::UNLOCKED, p.get_current_position(13).unwrap()); } #[test] @@ -703,23 +667,11 @@ pub mod tests { target_with_parameters: TargetWithParameters::Voting, amount: 10, }; - assert_eq!( - PositionState::LOCKING, - p.get_current_position(0, 2).unwrap() - ); - assert_eq!( - PositionState::LOCKING, - p.get_current_position(7, 2).unwrap() - ); - assert_eq!(PositionState::LOCKED, p.get_current_position(8, 2).unwrap()); - assert_eq!( - PositionState::LOCKED, - p.get_current_position(11, 2).unwrap() - ); - assert_eq!( - PositionState::LOCKED, - p.get_current_position(300, 2).unwrap() - ); + assert_eq!(PositionState::LOCKING, p.get_current_position(0).unwrap()); + assert_eq!(PositionState::LOCKING, p.get_current_position(7).unwrap()); + assert_eq!(PositionState::LOCKED, p.get_current_position(8).unwrap()); + assert_eq!(PositionState::LOCKED, p.get_current_position(11).unwrap()); + assert_eq!(PositionState::LOCKED, p.get_current_position(300).unwrap()); } #[test] #[allow(deprecated)] @@ -917,9 +869,9 @@ pub mod tests { u64, > = HashMap::new(); for &position in positions.iter() { - let current_state = position.get_current_position(epoch, 1).unwrap(); + let current_state = position.get_current_position(epoch).unwrap(); let previous_state = position - .get_current_position(epoch.saturating_sub(1), 1) + .get_current_position(epoch.saturating_sub(1)) .unwrap(); if current_state != PositionState::UNLOCKED { @@ -944,7 +896,6 @@ pub mod tests { dynamic_position_array .merge_target_positions( epoch, - 1, &mut next_index, TargetWithParameters::IntegrityPool { publisher: FIRST_PUBLISHER, @@ -952,12 +903,11 @@ pub mod tests { ) .unwrap(); dynamic_position_array - .merge_target_positions(epoch, 1, &mut next_index, TargetWithParameters::Voting) + .merge_target_positions(epoch, &mut next_index, TargetWithParameters::Voting) .unwrap(); dynamic_position_array .merge_target_positions( epoch, - 1, &mut next_index, TargetWithParameters::IntegrityPool { publisher: SECOND_PUBLISHER, @@ -973,9 +923,9 @@ pub mod tests { > = HashMap::new(); for i in 0..next_index { if let Some(position) = dynamic_position_array.read_position(i as usize).unwrap() { - let current_state = position.get_current_position(epoch, 1).unwrap(); + let current_state = position.get_current_position(epoch).unwrap(); let previous_state = position - .get_current_position(epoch.saturating_sub(1), 1) + .get_current_position(epoch.saturating_sub(1)) .unwrap(); if hash_set.contains(&( @@ -1027,9 +977,9 @@ pub mod tests { u64, > = HashMap::new(); for &position in positions.iter() { - let current_state = position.get_current_position(epoch, 1).unwrap(); + let current_state = position.get_current_position(epoch).unwrap(); let previous_state = position - .get_current_position(epoch.saturating_sub(1), 1) + .get_current_position(epoch.saturating_sub(1)) .unwrap(); pre_position_buckets @@ -1172,7 +1122,7 @@ pub mod tests { }; let governance_exposure = dynamic_position_array - .get_target_exposure(&Target::Voting, epoch, 1) + .get_target_exposure(&Target::Voting, epoch) .unwrap(); let publisher_1_exposure = pre_position_buckets @@ -1210,7 +1160,6 @@ pub mod tests { } = dynamic_position_array .slash_positions( epoch, - 1, &mut next_index, custody_account_amount, &FIRST_PUBLISHER, @@ -1226,9 +1175,9 @@ pub mod tests { > = HashMap::new(); for i in 0..next_index { if let Some(position) = dynamic_position_array.read_position(i as usize).unwrap() { - let current_state = position.get_current_position(epoch, 1).unwrap(); + let current_state = position.get_current_position(epoch).unwrap(); let previous_state = position - .get_current_position(epoch.saturating_sub(1), 1) + .get_current_position(epoch.saturating_sub(1)) .unwrap(); post_position_buckets @@ -1273,7 +1222,7 @@ pub mod tests { // check governance exposure has been reduced by the correct amount let post_governance_exposure = dynamic_position_array - .get_target_exposure(&Target::Voting, epoch, 1) + .get_target_exposure(&Target::Voting, epoch) .unwrap(); if post_governance_exposure diff --git a/staking/programs/staking/src/utils/clock.rs b/staking/programs/staking/src/utils/clock.rs index ad242cc13..ab5603701 100644 --- a/staking/programs/staking/src/utils/clock.rs +++ b/staking/programs/staking/src/utils/clock.rs @@ -10,6 +10,8 @@ use { std::convert::TryInto, }; +pub const UNLOCKING_DURATION: u64 = 1; // 1 epoch + /// Computes Pyth clock. /// Right now it's just the current Unix timestamp divided by the epoch length pub fn get_current_epoch(global_config: &GlobalConfig) -> Result { diff --git a/staking/programs/staking/src/utils/risk.rs b/staking/programs/staking/src/utils/risk.rs index 0de577b07..f2d47177d 100644 --- a/staking/programs/staking/src/utils/risk.rs +++ b/staking/programs/staking/src/utils/risk.rs @@ -24,18 +24,11 @@ pub fn validate( total_balance: u64, unvested_balance: u64, current_epoch: u64, - unlocking_duration: u8, ) -> Result { - let governance_exposure: u64 = stake_account_positions.get_target_exposure( - &Target::Voting, - current_epoch, - unlocking_duration, - )?; - let integrity_pool_exposure: u64 = stake_account_positions.get_target_exposure( - &Target::IntegrityPool, - current_epoch, - unlocking_duration, - )?; + let governance_exposure: u64 = + stake_account_positions.get_target_exposure(&Target::Voting, current_epoch)?; + let integrity_pool_exposure: u64 = + stake_account_positions.get_target_exposure(&Target::IntegrityPool, current_epoch)?; let vested_balance = total_balance .checked_sub(unvested_balance) @@ -125,17 +118,17 @@ pub mod tests { pd.read_position(0) .unwrap() .unwrap() - .get_current_position(current_epoch, 1) + .get_current_position(current_epoch) .unwrap(), desired_state ); - assert_eq!(validate(&pd, 15, 0, current_epoch, 1).unwrap(), 5); // 10 staked - assert_eq!(validate(&pd, 10, 0, current_epoch, 1).unwrap(), 0); // 10 staked, the limit - assert_eq!(validate(&pd, 13, 3, current_epoch, 1).unwrap(), 0); // 3 locked, 10 staked - assert!(validate(&pd, 9, 0, current_epoch, 1).is_err()); // 9 tokens but needs 10 staked, should fail - assert!(validate(&pd, 13, 4, current_epoch, 1).is_err()); // 4 locked, 9 unlocked but - // needs 10 for staking, - // should fail + assert_eq!(validate(&pd, 15, 0, current_epoch).unwrap(), 5); // 10 staked + assert_eq!(validate(&pd, 10, 0, current_epoch).unwrap(), 0); // 10 staked, the limit + assert_eq!(validate(&pd, 13, 3, current_epoch).unwrap(), 0); // 3 locked, 10 staked + assert!(validate(&pd, 9, 0, current_epoch).is_err()); // 9 tokens but needs 10 staked, should fail + assert!(validate(&pd, 13, 4, current_epoch).is_err()); // 4 locked, 9 unlocked but + // needs 10 for staking, + // should fail } let (current_epoch, desired_state) = (51u64, PositionState::UNLOCKED); @@ -143,15 +136,15 @@ pub mod tests { pd.read_position(0) .unwrap() .unwrap() - .get_current_position(current_epoch, 1) + .get_current_position(current_epoch) .unwrap(), desired_state ); - assert_eq!(validate(&pd, 15, 0, current_epoch, 1).unwrap(), 15); - assert_eq!(validate(&pd, 10, 0, current_epoch, 1).unwrap(), 10); - assert_eq!(validate(&pd, 13, 3, current_epoch, 1).unwrap(), 10); - assert_eq!(validate(&pd, 9, 0, current_epoch, 1).unwrap(), 9); - assert_eq!(validate(&pd, 13, 4, current_epoch, 1).unwrap(), 9); + assert_eq!(validate(&pd, 15, 0, current_epoch).unwrap(), 15); + assert_eq!(validate(&pd, 10, 0, current_epoch).unwrap(), 10); + assert_eq!(validate(&pd, 13, 3, current_epoch).unwrap(), 10); + assert_eq!(validate(&pd, 9, 0, current_epoch).unwrap(), 9); + assert_eq!(validate(&pd, 13, 4, current_epoch).unwrap(), 9); } #[test] @@ -182,12 +175,12 @@ pub mod tests { ) .unwrap(); let current_epoch = 44; - assert_eq!(validate(&pd, 10, 0, current_epoch, 1).unwrap(), 3); - assert_eq!(validate(&pd, 7, 0, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 7, 4, current_epoch, 1).unwrap(), 0); - assert!(validate(&pd, 6, 0, current_epoch, 1).is_err()); + assert_eq!(validate(&pd, 10, 0, current_epoch).unwrap(), 3); + assert_eq!(validate(&pd, 7, 0, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 7, 4, current_epoch).unwrap(), 0); + assert!(validate(&pd, 6, 0, current_epoch).is_err()); // only 2 vested: - assert!(validate(&pd, 10, 8, current_epoch, 1).is_err()); + assert!(validate(&pd, 10, 8, current_epoch).is_err()); } #[test] fn test_double_integrity_pool() { @@ -219,11 +212,11 @@ pub mod tests { ) .unwrap(); let current_epoch = 44; - assert_eq!(validate(&pd, 10, 0, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 12, 0, current_epoch, 1).unwrap(), 2); - assert!(validate(&pd, 12, 4, current_epoch, 1).is_err()); - assert!(validate(&pd, 9, 0, current_epoch, 1).is_err()); - assert!(validate(&pd, 20, 11, current_epoch, 1).is_err()); + assert_eq!(validate(&pd, 10, 0, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 12, 0, current_epoch).unwrap(), 2); + assert!(validate(&pd, 12, 4, current_epoch).is_err()); + assert!(validate(&pd, 9, 0, current_epoch).is_err()); + assert!(validate(&pd, 20, 11, current_epoch).is_err()); } #[test] fn test_multiple_integrity_pool() { @@ -244,7 +237,7 @@ pub mod tests { .unwrap(); } let current_epoch = 44; - assert_eq!(validate(&pd, 50, 0, current_epoch, 1).unwrap(), 0); + assert_eq!(validate(&pd, 50, 0, current_epoch).unwrap(), 0); // Now we have 6 integrity pool positions, so 50 tokens is not enough pd.write_position( 7, @@ -258,10 +251,10 @@ pub mod tests { }, ) .unwrap(); - assert!(validate(&pd, 50, 0, current_epoch, 1).is_err()); + assert!(validate(&pd, 50, 0, current_epoch).is_err()); // But 60 should be - assert_eq!(validate(&pd, 60, 0, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 65, 0, current_epoch, 1).unwrap(), 5); + assert_eq!(validate(&pd, 60, 0, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 65, 0, current_epoch).unwrap(), 5); } #[test] fn test_multiple_voting() { @@ -280,10 +273,10 @@ pub mod tests { .unwrap(); } let current_epoch = 44; - assert_eq!(validate(&pd, 100, 0, current_epoch, 1).unwrap(), 50); - assert_eq!(validate(&pd, 50, 0, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 60, 51, current_epoch, 1).unwrap(), 9); - assert!(validate(&pd, 49, 0, current_epoch, 1).is_err()); + assert_eq!(validate(&pd, 100, 0, current_epoch).unwrap(), 50); + assert_eq!(validate(&pd, 50, 0, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 60, 51, current_epoch).unwrap(), 9); + assert!(validate(&pd, 49, 0, current_epoch).is_err()); } #[test] @@ -304,7 +297,7 @@ pub mod tests { } // Overflows in the total governance computation let current_epoch = 44; - assert!(validate(&pd, u64::MAX, 0, current_epoch, 1).is_err()); + assert!(validate(&pd, u64::MAX, 0, current_epoch).is_err()); } #[test] @@ -327,7 +320,7 @@ pub mod tests { } let current_epoch = 44; // Overflows in the aggregation computation - assert!(validate(&pd, u64::MAX, 0, current_epoch, 1).is_err()); + assert!(validate(&pd, u64::MAX, 0, current_epoch).is_err()); } #[test] @@ -381,11 +374,11 @@ pub mod tests { ) .unwrap(); - assert_eq!(validate(&pd, 10, 6, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 10, 4, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 11, 7, current_epoch, 1).unwrap(), 0); - assert_eq!(validate(&pd, 11, 6, current_epoch, 1).unwrap(), 1); - assert!(validate(&pd, 10, 7, current_epoch, 1).is_err()); // breaks the integrity pool inequality - assert!(validate(&pd, 4, 0, current_epoch, 1).is_err()); // breaks the voting inequality + assert_eq!(validate(&pd, 10, 6, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 10, 4, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 11, 7, current_epoch).unwrap(), 0); + assert_eq!(validate(&pd, 11, 6, current_epoch).unwrap(), 1); + assert!(validate(&pd, 10, 7, current_epoch).is_err()); // breaks the integrity pool inequality + assert!(validate(&pd, 4, 0, current_epoch).is_err()); // breaks the voting inequality } } diff --git a/staking/programs/staking/src/utils/voter_weight.rs b/staking/programs/staking/src/utils/voter_weight.rs index 804fd0169..5b14920be 100644 --- a/staking/programs/staking/src/utils/voter_weight.rs +++ b/staking/programs/staking/src/utils/voter_weight.rs @@ -13,14 +13,13 @@ use { pub fn compute_voter_weight( stake_account_positions: &DynamicPositionArray, current_epoch: u64, - unlocking_duration: u8, current_locked: u64, total_supply: u64, ) -> Result { let mut raw_voter_weight = 0u64; for i in 0..stake_account_positions.get_position_capacity() { if let Some(position) = stake_account_positions.read_position(i)? { - match position.get_current_position(current_epoch, unlocking_duration)? { + match position.get_current_position(current_epoch)? { PositionState::LOCKED | PositionState::PREUNLOCKING => { if position.is_voting() { // position.amount is trusted, so I don't think this can overflow, @@ -104,19 +103,19 @@ pub mod tests { ) .unwrap(); - let weight = compute_voter_weight(&pd, 0, 1, 100, 150).unwrap(); + let weight = compute_voter_weight(&pd, 0, 100, 150).unwrap(); assert_eq!(weight, 0); - let weight = compute_voter_weight(&pd, 1, 1, 100, 150).unwrap(); + let weight = compute_voter_weight(&pd, 1, 100, 150).unwrap(); assert_eq!(weight, 7 * 150 / 100); - let weight = compute_voter_weight(&pd, 2, 1, 100, 150).unwrap(); + let weight = compute_voter_weight(&pd, 2, 100, 150).unwrap(); assert_eq!(weight, 12 * 150 / 100); - let weight = compute_voter_weight(&pd, 3, 1, 100, 150).unwrap(); + let weight = compute_voter_weight(&pd, 3, 100, 150).unwrap(); assert_eq!(weight, 8 * 150 / 100); - let weight = compute_voter_weight(&pd, 4, 1, 100, 150).unwrap(); + let weight = compute_voter_weight(&pd, 4, 100, 150).unwrap(); assert_eq!(weight, 3 * 150 / 100); } @@ -135,7 +134,7 @@ pub mod tests { ) .unwrap(); - let weight = compute_voter_weight(&pd, 1, 1, u64::MAX / 2, u64::MAX).unwrap(); + let weight = compute_voter_weight(&pd, 1, u64::MAX / 2, u64::MAX).unwrap(); assert_eq!(weight, u64::MAX); } @@ -154,7 +153,7 @@ pub mod tests { ) .unwrap(); - let weight = compute_voter_weight(&pd, 1, 1, 0, u64::MAX).unwrap(); + let weight = compute_voter_weight(&pd, 1, 0, u64::MAX).unwrap(); assert_eq!(weight, 0); } } diff --git a/staking/programs/staking/src/wasm.rs b/staking/programs/staking/src/wasm.rs index 2ae41123f..a0aec7fd6 100644 --- a/staking/programs/staking/src/wasm.rs +++ b/staking/programs/staking/src/wasm.rs @@ -55,22 +55,20 @@ impl WasmPositionData { &self, index: u16, current_epoch: u64, - unlocking_duration: u8, ) -> Result { - convert_error(self.get_position_state_impl(index, current_epoch, unlocking_duration)) + convert_error(self.get_position_state_impl(index, current_epoch)) } fn get_position_state_impl( &self, index: u16, current_epoch: u64, - unlocking_duration: u8, ) -> anchor_lang::Result { let mut account = DynamicPositionArrayAccount::default_with_data(&self.wrapped); account .to_dynamic_position_array() .read_position(index as usize)? .ok_or_else(|| error!(ErrorCode::PositionNotInUse))? - .get_current_position(current_epoch, unlocking_duration) + .get_current_position(current_epoch) } #[wasm_bindgen(js_name=isPositionVoting)] pub fn is_position_voting(&self, index: u16) -> Result { @@ -93,14 +91,12 @@ impl WasmPositionData { pub fn get_locked_balance_summary( &self, current_epoch: u64, - unlocking_duration: u8, ) -> Result { - convert_error(self.get_locked_balance_summary_impl(current_epoch, unlocking_duration)) + convert_error(self.get_locked_balance_summary_impl(current_epoch)) } fn get_locked_balance_summary_impl( &self, current_epoch: u64, - unlocking_duration: u8, ) -> anchor_lang::Result { let mut account = DynamicPositionArrayAccount::default_with_data(&self.wrapped); let positions = account.to_dynamic_position_array(); @@ -112,7 +108,7 @@ impl WasmPositionData { for i in 0..positions.get_position_capacity() { if let Some(position) = positions.read_position(i)? { - match position.get_current_position(current_epoch, unlocking_duration)? { + match position.get_current_position(current_epoch)? { PositionState::LOCKING => { locking = locking .checked_add(position.amount) @@ -149,14 +145,12 @@ impl WasmPositionData { pub fn get_voter_weight( &self, current_epoch: u64, - unlocking_duration: u8, current_locked: u64, ) -> Result { let mut account = DynamicPositionArrayAccount::default_with_data(&self.wrapped); convert_error(crate::utils::voter_weight::compute_voter_weight( &account.to_dynamic_position_array(), current_epoch, - unlocking_duration, current_locked, MAX_VOTER_WEIGHT, )) diff --git a/staking/target/idl/staking.json b/staking/target/idl/staking.json index 689407dae..2e9e4793d 100644 --- a/staking/target/idl/staking.json +++ b/staking/target/idl/staking.json @@ -2621,7 +2621,7 @@ "type": "pubkey" }, { - "name": "unlocking_duration", + "name": "removed_unlocking_duration", "type": "u8" }, { diff --git a/staking/target/types/staking.ts b/staking/target/types/staking.ts index 7fa6e3aad..db1096c49 100644 --- a/staking/target/types/staking.ts +++ b/staking/target/types/staking.ts @@ -2627,7 +2627,7 @@ export type Staking = { "type": "pubkey" }, { - "name": "unlockingDuration", + "name": "removedUnlockingDuration", "type": "u8" }, { diff --git a/staking/tests/config.ts b/staking/tests/config.ts index f234b95db..3a1539396 100644 --- a/staking/tests/config.ts +++ b/staking/tests/config.ts @@ -66,7 +66,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -99,7 +99,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -126,7 +126,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -149,7 +149,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -176,7 +176,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -199,7 +199,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -224,7 +224,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -332,7 +332,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: program.provider.publicKey, @@ -356,7 +356,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority, @@ -384,7 +384,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority, @@ -407,7 +407,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority, @@ -438,7 +438,7 @@ describe("config", async () => { governanceAuthority: program.provider.publicKey, pythTokenMint: pythMintAccount.publicKey, pythGovernanceRealm, - unlockingDuration: 2, + removedUnlockingDuration: 0, epochDuration: new BN(3600), freeze: false, pdaAuthority: pdaAuthority,