Skip to content

Commit

Permalink
SingularStakingInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinonard committed Oct 2, 2023
1 parent 5cab4c6 commit 8501e31
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 13 deletions.
35 changes: 35 additions & 0 deletions pallets/dapp-staking-v3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ pub mod pallet {
NoUnlockedChunksToClaim,
/// There are no unlocking chunks available to relock.
NoUnlockingChunks,
/// The amount being staked is too large compared to what's available for staking.
UnavailableStakeFunds,
/// There are unclaimed rewards remaining from past periods. They should be claimed before staking again.
UnclaimedRewardsFromPastPeriods,
/// Cannot add additional stake chunks due to size limit.
TooManyStakeChunks,
/// An unexpected error occured while trying to stake.
InternalStakeError,
}

/// General information about dApp staking protocol state.
Expand All @@ -219,6 +227,18 @@ pub mod pallet {
pub type Ledger<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, AccountLedgerFor<T>, ValueQuery>;

/// Information about how much each staker has staked for each smart contract in some period.
#[pallet::storage]
pub type StakerInfo<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
T::SmartContract,
SingularStakingInfo,
OptionQuery,
>;

/// General information about the current era.
#[pallet::storage]
pub type CurrentEraInfo<T: Config> = StorageValue<_, EraInfo, ValueQuery>;
Expand Down Expand Up @@ -587,6 +607,21 @@ pub mod pallet {
let protocol_state = ActiveProtocolState::<T>::get();
let mut ledger = Ledger::<T>::get(&account);

// Increase stake amount for the current era & period.
ledger
.add_stake_amount(amount, protocol_state.era, protocol_state.period)
.map_err(|err| match err {
AccountLedgerError::InvalidPeriod => {
Error::<T>::UnclaimedRewardsFromPastPeriods
}
AccountLedgerError::UnavailableStakeFunds => Error::<T>::UnavailableStakeFunds,
AccountLedgerError::NoCapacity => Error::<T>::TooManyStakeChunks,
AccountLedgerError::OldEra => Error::<T>::InternalStakeError,
})?;

// TODO: maybe keep track of pending bonus rewards in the AccountLedger struct?
// That way it's easy to check if stake can even be called - bonus-rewards should be zero & last staked era should be None or current one.

// is it voting or b&e period?
// how much does user have available for staking?
// has user claimed past rewards? Can we force them to do it before they start staking again?
Expand Down
5 changes: 4 additions & 1 deletion pallets/dapp-staking-v3/src/test/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ impl ExtBuilder {
era: 1,
next_era_start: BlockNumber::from(101_u32),
period: 1,
period_type: PeriodType::Voting(16),
period_type_and_ending_era: PeriodTypeAndEndingEra {
period_type: PeriodType::Voting,
ending_era: 16,
},
maintenance: false,
});
});
Expand Down
8 changes: 4 additions & 4 deletions pallets/dapp-staking-v3/src/test/tests_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,7 @@ fn add_stake_amount_too_large_amount_fails() {
// Sanity check
assert_eq!(
acc_ledger.add_stake_amount(10, 1, 1),
Err(AccountLedgerError::TooLargeStakeAmount)
Err(AccountLedgerError::UnavailableStakeFunds)
);

// Lock some amount, and try to stake more than that
Expand All @@ -1022,7 +1022,7 @@ fn add_stake_amount_too_large_amount_fails() {
assert!(acc_ledger.add_lock_amount(lock_amount, first_era).is_ok());
assert_eq!(
acc_ledger.add_stake_amount(lock_amount + 1, first_era, first_period),
Err(AccountLedgerError::TooLargeStakeAmount)
Err(AccountLedgerError::UnavailableStakeFunds)
);

// Additional check - have some active stake, and then try to overstake
Expand All @@ -1031,12 +1031,12 @@ fn add_stake_amount_too_large_amount_fails() {
.is_ok());
assert_eq!(
acc_ledger.add_stake_amount(3, first_era, first_period),
Err(AccountLedgerError::TooLargeStakeAmount)
Err(AccountLedgerError::UnavailableStakeFunds)
);
}

#[test]
fn add_stake_amount_exceeding_capacity_fails() {
fn add_stake_amount_while_exceeding_capacity_fails() {
get_u32_type!(LockedDummy, 5);
get_u32_type!(UnlockingDummy, 5);
get_u32_type!(StakingDummy, 8);
Expand Down
127 changes: 119 additions & 8 deletions pallets/dapp-staking-v3/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub enum AccountLedgerError {
/// Invalid period specified.
InvalidPeriod,
/// Stake amount is to large in respect to what's available.
TooLargeStakeAmount,
UnavailableStakeFunds,
}

/// Helper struct for easier manipulation of sparse <amount, era> pairs.
Expand Down Expand Up @@ -231,11 +231,17 @@ where
#[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
pub enum PeriodType {
/// Period during which the focus is on voting.
/// Inner value is the era in which the voting period ends.
Voting(#[codec(compact)] EraNumber),
Voting,
/// Period during which dApps and stakers earn rewards.
/// Inner value is the era in which the Build&Eearn period ends.
BuildAndEarn(#[codec(compact)] EraNumber),
BuildAndEarn,
}

/// Wrapper type around current `PeriodType` and era number when it's expected to end.
#[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)]
pub struct PeriodTypeAndEndingEra {
pub period_type: PeriodType,
#[codec(compact)]
pub ending_era: EraNumber,
}

/// Force types to speed up the next era, and even period.
Expand All @@ -262,7 +268,7 @@ pub struct ProtocolState<BlockNumber: AtLeast32BitUnsigned + MaxEncodedLen> {
#[codec(compact)]
pub period: PeriodNumber,
/// Ongoing period type and when is it expected to end.
pub period_type: PeriodType,
pub period_type_and_ending_era: PeriodTypeAndEndingEra,
/// `true` if pallet is in maintenance mode (disabled), `false` otherwise.
/// TODO: provide some configurable barrier to handle this on the runtime level instead? Make an item for this?
pub maintenance: bool,
Expand All @@ -277,7 +283,10 @@ where
era: 0,
next_era_start: BlockNumber::from(1_u32),
period: 0,
period_type: PeriodType::Voting(0),
period_type_and_ending_era: PeriodTypeAndEndingEra {
period_type: PeriodType::Voting,
ending_era: 2,
},
maintenance: false,
}
}
Expand Down Expand Up @@ -546,7 +555,7 @@ where
}

if self.stakeable_amount(current_period) < amount {
return Err(AccountLedgerError::TooLargeStakeAmount);
return Err(AccountLedgerError::UnavailableStakeFunds);
}

self.staked.add_amount(amount, era)?;
Expand Down Expand Up @@ -702,3 +711,105 @@ impl EraInfo {
self.unlocking.saturating_reduce(amount);
}
}

/// Information about how much a particular staker staked on a particular smart contract.
///
/// Keeps track of amount staked in the 'voting period', as well as 'build&earn period'.
#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Debug, PartialEq, Eq, TypeInfo, Default)]
pub struct SingularStakingInfo {
/// Total amount staked during the voting period.
#[codec(compact)]
vp_staked_amount: Balance,
/// Total amount staked during the build&earn period.
#[codec(compact)]
bep_staked_amount: Balance,
/// Period number for which this entry is relevant.
#[codec(compact)]
period: PeriodNumber,
/// Indicates whether a staker is a loyal staker or not.
loyal_staker: bool,
/// Indicates whether staker claimed rewards
reward_claimed: bool,
}

impl SingularStakingInfo {
/// Creates new instance of the struct.
///
/// ## Args
///
/// `period` - period number for which this entry is relevant.
/// `period_type` - period type during which this entry is created.
pub fn new(period: PeriodNumber, period_type: PeriodType) -> Self {
Self {
vp_staked_amount: Balance::zero(),
bep_staked_amount: Balance::zero(),
period,
// Loyalty staking is only possible if stake is first made during the voting period.
loyal_staker: period_type == PeriodType::Voting,
reward_claimed: false,
}
}

/// Stake the specified amount on the contract, for the specified period type.
pub fn stake(&mut self, amount: Balance, period_type: PeriodType) {
if period_type == PeriodType::Voting {
self.vp_staked_amount.saturating_accrue(amount);
} else {
self.bep_staked_amount.saturating_accrue(amount);
}
}

/// Unstakes some of the specified amount from the contract.
///
/// In case the `amount` being unstaked is larger than the amount staked in the `voting period`,
/// and `voting period` has passed, this will remove the _loyalty_ flag from the staker.
///
/// Returns the amount that was unstaked from the `voting period` stake.
// TODO: Maybe both unstake values should be returned?
pub fn unstake(&mut self, amount: Balance, period_type: PeriodType) -> Balance {
// If B&E period stake can cover the unstaking amount, just reduce it.
if self.bep_staked_amount >= amount {
self.bep_staked_amount.saturating_reduce(amount);
Balance::zero()
} else {
// In case we have to dip into the voting period stake, make sure B&E period stake is reduced first.
// Also make sure to remove loyalty flag from the staker.
let leftover_amount = amount.saturating_sub(self.bep_staked_amount);
self.bep_staked_amount = Balance::zero();

let vp_staked_amount_snapshot = self.vp_staked_amount;
self.vp_staked_amount.saturating_reduce(leftover_amount);
self.bep_staked_amount = Balance::zero();

// It's ok if staker reduces their stake amount during voting period.
self.loyal_staker = period_type == PeriodType::Voting;

// Actual amount that was unstaked
vp_staked_amount_snapshot.saturating_sub(self.vp_staked_amount)
}
}

/// Total staked on the contract by the user. Both period type stakes are included.
pub fn total_staked_amount(&self) -> Balance {
self.vp_staked_amount.saturating_add(self.bep_staked_amount)
}

/// Returns amount staked in the specified period.
pub fn staked_amount(&self, period_type: PeriodType) -> Balance {
if period_type == PeriodType::Voting {
self.vp_staked_amount
} else {
self.bep_staked_amount
}
}

/// If `true` staker has staked during voting period and has never reduced their sta
pub fn is_loyal(&self) -> bool {
self.loyal_staker
}

/// Period for which this entry is relevant.
pub fn period_number(&self) -> PeriodNumber {
self.period
}
}

0 comments on commit 8501e31

Please sign in to comment.