Skip to content

Commit

Permalink
Test utils for claim-staker
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinonard committed Oct 13, 2023
1 parent fff0656 commit 0daae3a
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 10 deletions.
22 changes: 14 additions & 8 deletions pallets/dapp-staking-v3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ pub mod pallet {
}

let mut era_info = CurrentEraInfo::<T>::get();
let staker_reward_pool = Balance::from(1000_u128); // TODO: calculate this properly
let staker_reward_pool = Balance::from(1000_u128); // TODO: calculate this properly, inject it from outside (Tokenomics 2.0 pallet?)
let era_reward = EraReward::new(staker_reward_pool, era_info.total_staked_amount());
let ending_era = protocol_state.era;
let next_era = ending_era.saturating_add(1);
Expand Down Expand Up @@ -985,13 +985,17 @@ pub mod pallet {
/// TODO
#[pallet::call_index(11)]
#[pallet::weight(Weight::zero())]
pub fn claim_staker_reward(origin: OriginFor<T>) -> DispatchResult {
pub fn claim_staker_rewards(origin: OriginFor<T>) -> DispatchResult {
Self::ensure_pallet_enabled()?;
let account = ensure_signed(origin)?;

let protocol_state = ActiveProtocolState::<T>::get();
let mut ledger = Ledger::<T>::get(&account);

// TODO: how do we handle expired rewards? Add an additional call to clean them up?
// Putting this logic inside existing calls will add even more complexity.

// Check if the rewards have expired
let staked_period = ledger.staked_period.ok_or(Error::<T>::NoClaimableRewards)?;
ensure!(
staked_period
Expand Down Expand Up @@ -1026,9 +1030,11 @@ pub mod pallet {
.staked
.left_split(last_claim_era)
.map_err(|_| Error::<T>::InternalClaimStakerError)?;
ensure!(chunks_for_claim.0.len() > 0, Error::<T>::NoClaimableRewards);

// Calculate rewards
let mut rewards: Vec<_> = Vec::new();
let mut reward_sum = Balance::zero();
for era in first_claim_era..=last_claim_era {
let era_reward = era_rewards
.get(era)
Expand All @@ -1041,14 +1047,14 @@ pub mod pallet {
let staker_reward = Perbill::from_rational(chunk.get_amount(), era_reward.staked())
* era_reward.staker_reward_pool();
rewards.push((era, staker_reward));
reward_sum.saturating_accrue(staker_reward);
}

let reward_sum = rewards.iter().fold(Balance::zero(), |acc, (_, reward)| {
acc.saturating_add(*reward)
});

// TODO; update & write ledger
if is_full_period_claimed {}
// Write updated ledger back to storage
if is_full_period_claimed {
ledger.all_stake_rewards_claimed();
}
Self::update_ledger(&account, ledger);

T::Currency::deposit_into_existing(&account, reward_sum)
.map_err(|_| Error::<T>::InternalClaimStakerError)?;
Expand Down
15 changes: 15 additions & 0 deletions pallets/dapp-staking-v3/src/test/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,18 @@ pub(crate) fn _advance_to_next_period_type() {
run_for_blocks(1);
}
}

// Return all dApp staking events from the event buffer.
pub fn dapp_staking_events() -> Vec<crate::Event<Test>> {
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let RuntimeEvent::DappStaking(inner) = e {
Some(inner)
} else {
None
}
})
.collect()
}
136 changes: 134 additions & 2 deletions pallets/dapp-staking-v3/src/test/testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ use crate::test::mock::*;
use crate::types::*;
use crate::{
pallet as pallet_dapp_staking, ActiveProtocolState, BlockNumberFor, ContractStake,
CurrentEraInfo, DAppId, Event, IntegratedDApps, Ledger, NextDAppId, StakerInfo,
CurrentEraInfo, DAppId, EraRewards, Event, IntegratedDApps, Ledger, NextDAppId, PeriodEnd,
PeriodEndInfo, StakerInfo,
};

use frame_support::{assert_ok, traits::Get};
use sp_runtime::traits::Zero;
use sp_runtime::{traits::Zero, Perbill};
use std::collections::HashMap;

/// Helper struct used to store the entire pallet state snapshot.
Expand All @@ -48,6 +49,11 @@ pub(crate) struct MemorySnapshot {
>,
contract_stake:
HashMap<<Test as pallet_dapp_staking::Config>::SmartContract, ContractStakingInfoSeries>,
era_rewards: HashMap<
EraNumber,
EraRewardSpan<<Test as pallet_dapp_staking::Config>::EraRewardSpanLength>,
>,
period_end: HashMap<PeriodNumber, PeriodEndInfo>,
}

impl MemorySnapshot {
Expand All @@ -63,6 +69,8 @@ impl MemorySnapshot {
.map(|(k1, k2, v)| ((k1, k2), v))
.collect(),
contract_stake: ContractStake::<Test>::iter().collect(),
era_rewards: EraRewards::<Test>::iter().collect(),
period_end: PeriodEnd::<Test>::iter().collect(),
}
}

Expand Down Expand Up @@ -742,3 +750,127 @@ pub(crate) fn assert_unstake(
);
}
}

/// Claim staker rewards.
pub(crate) fn assert_claim_staker_rewards(account: AccountId) {
let pre_snapshot = MemorySnapshot::new();
let pre_ledger = pre_snapshot.ledger.get(&account).unwrap();
let pre_total_issuance = <Test as pallet_dapp_staking::Config>::Currency::total_issuance();
let pre_free_balance = <Test as pallet_dapp_staking::Config>::Currency::free_balance(&account);

// Get the first eligible era for claiming rewards
let first_claim_era = pre_ledger
.staked
.0
.first()
.expect("Entry must exist, otherwise 'claim' is invalid.")
.get_era();

// Get the apprropriate era rewards span for the 'first era'
let era_span_length: EraNumber =
<Test as pallet_dapp_staking::Config>::EraRewardSpanLength::get();
let era_span_index = first_claim_era - (first_claim_era % era_span_length);
let era_rewards_span = pre_snapshot
.era_rewards
.get(&era_span_index)
.expect("Entry must exist, otherwise 'claim' is invalid.");

// Calculate the final era for claiming rewards. Also determine if this will fully claim all staked period rewards.
let is_current_period_stake = match pre_ledger.staked_period {
Some(staked_period)
if staked_period == pre_snapshot.active_protocol_state.period_number() =>
{
true
}
_ => false,
};

let (last_claim_era, is_full_claim) = if is_current_period_stake {
(pre_snapshot.active_protocol_state.era - 1, false)
} else {
let last_claim_era = era_rewards_span.last_era();

let claim_period = pre_ledger.staked_period.unwrap();
let period_end = pre_snapshot
.period_end
.get(&claim_period)
.expect("Entry must exist, since it's a past period.");

let last_claim_era = era_rewards_span.last_era().min(period_end.final_era);
let is_full_claim = last_claim_era == period_end.final_era;
(last_claim_era, is_full_claim)
};

assert!(
last_claim_era < pre_snapshot.active_protocol_state.era,
"Sanity check."
);

// Calculate the expected rewards
let mut rewards = Vec::new();
for era in first_claim_era..=last_claim_era {
let era_reward_info = era_rewards_span
.get(era)
.expect("Entry must exist, otherwise 'claim' is invalid.");
let stake_chunk = pre_ledger
.staked
.get(era)
.expect("Entry must exist, otherwise 'claim' is invalid.");

let reward = Perbill::from_rational(stake_chunk.amount, era_reward_info.staked())
* era_reward_info.staker_reward_pool();
rewards.push((era, reward));
}
let total_reward = rewards
.iter()
.fold(Balance::zero(), |acc, (_, reward)| acc + reward);

// Unstake from smart contract & verify event(s)
assert_ok!(DappStaking::claim_staker_rewards(RuntimeOrigin::signed(
account
),));

let events = dapp_staking_events();
assert_eq!(events.len(), rewards.len());
for (event, (era, reward)) in events.iter().zip(rewards.iter()) {
assert_eq!(
event,
&Event::<Test>::Reward {
account,
era: *era,
amount: *reward,
}
);
}

// Verify post state

let post_total_issuance = <Test as pallet_dapp_staking::Config>::Currency::total_issuance();
assert_eq!(
post_total_issuance,
pre_total_issuance + total_reward,
"Total issuance must increase by the total reward amount."
);

let post_free_balance = <Test as pallet_dapp_staking::Config>::Currency::free_balance(&account);
assert_eq!(
post_free_balance,
pre_free_balance + total_reward,
"Free balance must increase by the total reward amount."
);

let post_snapshot = MemorySnapshot::new();
let post_ledger = post_snapshot.ledger.get(&account).unwrap();

if is_full_claim {
assert!(post_ledger.staked.0.is_empty());
assert!(post_ledger.staked_period.is_none());
} else {
let stake_chunk = post_ledger.staked.0.first().expect("Entry must exist");
assert_eq!(stake_chunk.era, last_claim_era + 1);
assert_eq!(
stake_chunk.amount,
pre_ledger.staked.get(last_claim_era).unwrap().amount
);
}
}
1 change: 1 addition & 0 deletions pallets/dapp-staking-v3/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ where

/// Notify ledger that all `stake` rewards have been claimed for the staked era.
pub fn all_stake_rewards_claimed(&mut self) {
// TODO: improve handling once bonus reward tracking is added
self.staked = SparseBoundedAmountEraVec(BoundedVec::<StakeChunk, StakedLen>::default());
self.staked_period = None;
}
Expand Down

0 comments on commit 0daae3a

Please sign in to comment.