Skip to content

Commit

Permalink
claim_staking_rewards extrinsic (#2080)
Browse files Browse the repository at this point in the history
The goal of this PR is to implement the claim_staking_rewards extrinsic, its benchmarks, and tests.

Closes #1970
  • Loading branch information
shannonwells committed Jul 23, 2024
1 parent 0b35226 commit 6ed6217
Show file tree
Hide file tree
Showing 13 changed files with 490 additions and 169 deletions.
21 changes: 21 additions & 0 deletions common/primitives/src/capacity.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::msa::MessageSourceId;
use frame_support::traits::tokens::Balance;
use scale_info::TypeInfo;
use sp_core::{Decode, Encode, MaxEncodedLen, RuntimeDebug};
use sp_runtime::DispatchError;

/// The type of a Reward Era
Expand Down Expand Up @@ -55,3 +57,22 @@ pub trait Replenishable {
/// Checks if an account can be replenished.
fn can_replenish(msa_id: MessageSourceId) -> bool;
}

/// Result of checking a Boost History item to see if it's eligible for a reward.
#[derive(
Copy, Clone, Default, Encode, Eq, Decode, RuntimeDebug, MaxEncodedLen, PartialEq, TypeInfo,
)]

pub struct UnclaimedRewardInfo<Balance, BlockNumber> {
/// The Reward Era for which this reward was earned
pub reward_era: RewardEra,
/// When this reward expires, i.e. can no longer be claimed
pub expires_at_block: BlockNumber,
/// The total staked in this era as of the current block
pub staked_amount: Balance,
/// The amount staked in this era that is eligible for rewards. Does not count additional amounts
/// staked in this era.
pub eligible_amount: Balance,
/// The amount in token of the reward (only if it can be calculated using only on chain data)
pub earned_amount: Balance,
}
7 changes: 3 additions & 4 deletions e2e/capacity/change_staking_target.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { getFundingSource } from '../scaffolding/funding';
import {
createKeys, createMsaAndProvider,
stakeToProvider,
CENTS, DOLLARS, createAndFundKeypair, createProviderKeysAndId
} from "../scaffolding/helpers";
CENTS, DOLLARS, createAndFundKeypair, createProviderKeysAndId, getNonce,
} from '../scaffolding/helpers';
import { KeyringPair } from '@polkadot/keyring/types';

const fundingSource = getFundingSource('capacity-replenishment');

Expand Down Expand Up @@ -39,8 +40,6 @@ describe("Capacity: change_staking_target", function() {
await assert.rejects(call.signAndSend(),
(err) => {
assert. strictEqual(err?.name, 'InvalidTarget', `expected InvalidTarget, got ${err?.name}`);
// // {name: "InvalidTarget"}
// assert. strictEqual(err?.message, `Wrong value: expected`);
return true;
});
});
Expand Down
60 changes: 46 additions & 14 deletions pallets/capacity/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use parity_scale_codec::alloc::vec::Vec;

const SEED: u32 = 0;
const REWARD_POOL_TOTAL: u32 = 2_000_000;

fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
Expand Down Expand Up @@ -87,19 +88,36 @@ fn fill_unlock_chunks<T: Config>(caller: &T::AccountId, count: u32) {
UnstakeUnlocks::<T>::set(caller, Some(unlocking));
}

fn fill_reward_pool_chunks<T: Config>() {
let chunk_len = T::RewardPoolChunkLength::get();
let chunks = T::ProviderBoostHistoryLimit::get() / (chunk_len);
for i in 0..chunks {
let mut new_chunk = RewardPoolHistoryChunk::<T>::new();
for j in 0..chunk_len {
let era = (i + 1) * (j + 1);
assert_ok!(new_chunk.try_insert(era.into(), (1000u32 * era).into()));
}
ProviderBoostRewardPools::<T>::set(i, Some(new_chunk));
fn fill_reward_pool_chunks<T: Config>(current_era: RewardEra) {
let history_limit: RewardEra = <T as Config>::ProviderBoostHistoryLimit::get();
let starting_era: RewardEra = current_era - history_limit - 1u32;
for era in starting_era..current_era {
Capacity::<T>::update_provider_boost_reward_pool(era, REWARD_POOL_TOTAL.into());
}
}

fn fill_boost_history<T: Config>(
caller: &T::AccountId,
amount: BalanceOf<T>,
current_era: RewardEra,
) {
let max_history: RewardEra = <T as Config>::ProviderBoostHistoryLimit::get().into();
let starting_era = current_era - max_history - 1u32;
for i in starting_era..current_era {
assert_ok!(Capacity::<T>::upsert_boost_history(caller.into(), i, amount, true));
}
}

fn unclaimed_rewards_total<T: Config>(caller: &T::AccountId) -> BalanceOf<T> {
let zero_balance: BalanceOf<T> = 0u32.into();
let rewards: Vec<UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>> =
Capacity::<T>::list_unclaimed_rewards(caller).unwrap_or_default().to_vec();
rewards
.iter()
.fold(zero_balance, |acc, reward_info| acc.saturating_add(reward_info.earned_amount))
.into()
}

benchmarks! {
stake {
let caller: T::AccountId = create_funded_account::<T>("account", SEED, 105u32);
Expand Down Expand Up @@ -152,7 +170,7 @@ benchmarks! {

let current_era: RewardEra = (history_limit + 1u32).into();
CurrentEraInfo::<T>::set(RewardEraInfo{ era_index: current_era, started_at });
fill_reward_pool_chunks::<T>();
fill_reward_pool_chunks::<T>(current_era);
}: {
Capacity::<T>::start_new_reward_era_if_needed(current_block);
} verify {
Expand Down Expand Up @@ -232,12 +250,26 @@ benchmarks! {

}: _ (RawOrigin::Signed(caller.clone()), target, boost_amount)
verify {
assert!(StakingAccountLedger::<T>::contains_key(&caller));
assert!(StakingTargetLedger::<T>::contains_key(&caller, target));
assert!(CapacityLedger::<T>::contains_key(target));
assert_last_event::<T>(Event::<T>::ProviderBoosted {account: caller, amount: boost_amount, target, capacity}.into());
}

// TODO: vary the boost_history to get better weight estimates.
claim_staking_rewards {
let caller: T::AccountId = create_funded_account::<T>("account", SEED, 5u32);
let from_msa = 33;
let boost_amount: BalanceOf<T> = T::MinimumStakingAmount::get();
setup_provider_stake::<T>(&caller, &from_msa, boost_amount, false);
frame_system::Pallet::<T>::set_block_number(1002u32.into());
let current_era: RewardEra = 100;
set_era_and_reward_pool_at_block::<T>(current_era, 1001u32.into(), REWARD_POOL_TOTAL.into());
fill_reward_pool_chunks::<T>(current_era);
fill_boost_history::<T>(&caller, 100u32.into(), current_era);
let unclaimed_rewards = unclaimed_rewards_total::<T>(&caller);
}: _ (RawOrigin::Signed(caller.clone()))
verify {
assert_last_event::<T>(Event::<T>::ProviderBoostRewardClaimed {account: caller.clone(), reward_amount: unclaimed_rewards}.into());
}

impl_benchmark_test_suite!(Capacity,
tests::mock::new_test_ext(),
tests::mock::Test);
Expand Down
Loading

0 comments on commit 6ed6217

Please sign in to comment.