From e019e2645035fe9a5daa262673ed1e59df14afad Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:02:52 +0200 Subject: [PATCH 01/10] reward trait --- primitives/core/Cargo.toml | 1 + primitives/core/src/lib.rs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 197e4e539..996071a5c 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -17,6 +17,7 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } +frame-support = { workspace = true } # Cumulus cumulus-primitives-core = { workspace = true } diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 52d2e2a93..76bb620e7 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -16,10 +16,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_runtime::{ - generic, - traits::{BlakeTwo256, IdentifyAccount, Verify}, - MultiAddress, MultiSignature, OpaqueExtrinsic, +use { + frame_support::pallet_prelude::DispatchResultWithPostInfo, + sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, OpaqueExtrinsic, + }, }; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. @@ -96,3 +99,7 @@ pub mod well_known_keys { pub const SESSION_INDEX: &[u8] = &hex_literal::hex!["cec5070d609dd3497f72bde07fc96ba072763800a36a99fdfc7c10f6415f6ee6"]; } + +pub trait DistributeRewards { + fn distribute_rewards(rewarded: AccountId, amount: Balance) -> DispatchResultWithPostInfo; +} From 14aa1c1c81634643955c63563dca41ac59edbb8f Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:05:25 +0200 Subject: [PATCH 02/10] reward distribution code --- Cargo.lock | 2 + pallets/pooled-staking/Cargo.toml | 2 + pallets/pooled-staking/src/lib.rs | 12 +- pallets/pooled-staking/src/pools.rs | 166 +++++++++++++++++++++++++++- 4 files changed, 177 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2855c4ad..bb626c50f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7818,6 +7818,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "tp-core", ] [[package]] @@ -14606,6 +14607,7 @@ name = "tp-core" version = "0.1.0" dependencies = [ "cumulus-primitives-core", + "frame-support", "hex-literal 0.3.4", "parity-scale-codec", "sp-core", diff --git a/pallets/pooled-staking/Cargo.toml b/pallets/pooled-staking/Cargo.toml index b0b04a7d8..47a6eca9b 100644 --- a/pallets/pooled-staking/Cargo.toml +++ b/pallets/pooled-staking/Cargo.toml @@ -12,6 +12,7 @@ targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] log = { workspace = true } serde = { workspace = true, optional = true } +tp-core = { workspace = true } # Substrate frame-benchmarking = { workspace = true, optional = true } @@ -44,6 +45,7 @@ std = [ "serde", "sp-runtime/std", "sp-std/std", + "tp-core/std", ] runtime-benchmarks = [ "frame-benchmarking" ] try-runtime = [ "frame-support/try-runtime" ] diff --git a/pallets/pooled-staking/src/lib.rs b/pallets/pooled-staking/src/lib.rs index c923a2afd..7741bf406 100644 --- a/pallets/pooled-staking/src/lib.rs +++ b/pallets/pooled-staking/src/lib.rs @@ -51,8 +51,7 @@ pub mod weights; use frame_support::pallet; -pub use candidate::EligibleCandidate; -pub use pallet::*; +pub use {candidate::EligibleCandidate, pallet::*}; #[pallet(dev_mode)] pub mod pallet { @@ -517,4 +516,13 @@ pub mod pallet { Calls::::update_candidate_position(&candidates) } } + + impl tp_core::DistributeRewards, T::Balance> for Pallet { + fn distribute_rewards( + candidate: Candidate, + rewards: T::Balance, + ) -> DispatchResultWithPostInfo { + pools::distribute_rewards::(&candidate, rewards) + } + } } diff --git a/pallets/pooled-staking/src/pools.rs b/pallets/pooled-staking/src/pools.rs index 635161f1f..3271db3d8 100644 --- a/pallets/pooled-staking/src/pools.rs +++ b/pallets/pooled-staking/src/pools.rs @@ -16,11 +16,16 @@ use { crate::{ + candidate::Candidates, traits::{ErrAdd, ErrMul, ErrSub, MulDiv}, - Candidate, Config, Delegator, Error, Pools, PoolsKey, Shares, Stake, + Candidate, Config, Delegator, Error, Event, Pallet, Pools, PoolsKey, Shares, Stake, }, core::marker::PhantomData, - frame_support::ensure, + frame_support::{ + ensure, + pallet_prelude::DispatchResultWithPostInfo, + traits::{fungible::Mutate, tokens::Preservation}, + }, sp_core::Get, sp_runtime::traits::{CheckedAdd, CheckedDiv, Zero}, }; @@ -337,7 +342,6 @@ impl_pool!( ); impl ManualRewards { - #[allow(dead_code)] pub fn pending_rewards( candidate: &Candidate, delegator: &Delegator, @@ -361,6 +365,34 @@ impl ManualRewards { Ok(Stake(diff.err_mul(&shares.0)?)) } + /// Increase the rewards of the ManualRewards pool with best effort. + /// Returns the actual amount distributed (after rounding). + pub fn increase_rewards( + candidate: &Candidate, + rewards: Stake, + ) -> Result, Error> { + let Shares(supply) = Self::shares_supply(&candidate); + if supply.is_zero() { + return Ok(Stake(Zero::zero())); + } + + let rewards_per_share = rewards + .0 + .checked_div(&supply) + .ok_or(Error::::InconsistentState)?; + if rewards_per_share.is_zero() { + return Ok(Stake(Zero::zero())); + } + + let rewards = rewards_per_share.err_mul(&supply)?; + + let counter = Pools::::get(candidate, &PoolsKey::ManualRewardsCounter); + let counter = counter.err_add(&rewards_per_share)?; + Pools::::set(candidate, &PoolsKey::ManualRewardsCounter, counter); + + Ok(Stake(rewards)) + } + pub fn claim_rewards( candidate: &Candidate, delegator: &Delegator, @@ -396,3 +428,131 @@ impl ManualRewards { Ok(Stake(rewards)) } } + +/// Perform rewards distribution for the provided candidate. +/// +/// The pallet considered that it already posses the rewards in its account, +/// and it is the responsibility of the caller to transfer or mint the currency +/// to the staking pallet account. +/// +/// Rewards are split using `RewardsCollatorCommission` between the candidate +/// and all the delegators (including the candidate self-delegation). For each, +/// the rewards are then split according to the value of all the ManualRewards +/// and AutoCompounding shares. +/// +/// As candidate rewards will give increase the candidate auto compounding +/// self-delegation, the delegator rewards are distributed first. ManualRewards +/// pool rewards are first distributed by increasing the pool counter, which may +/// result in some rounding. As distributing the AutoCompounding pool rewards +/// consists of simply increasing `AutoCompoundingSharesTotalStaked`, it is not +/// subject to rounding and it can absorb the rounding dust from ManualRewards +/// reward distribution. +/// +/// Then it is time to distribute the candidate dedicated rewards. For +/// AutoCompounding, it is as if the candidate received the rewards then +/// self-delegated (instantly). It is thus implemented by creating new +/// AutoCompounding shares. This can lead to some rounding, which will be +/// absorbed in the ManualRewards distribution, which simply consist of +/// transfering the funds to the candidate account. +#[allow(dead_code)] +pub fn distribute_rewards( + candidate: &Candidate, + rewards: T::Balance, +) -> DispatchResultWithPostInfo { + let candidate_manual_rewards = distribute_rewards_inner::(candidate, rewards)?; + + if !candidate_manual_rewards.is_zero() { + T::Currency::transfer( + &T::StakingAccount::get(), + &candidate, + candidate_manual_rewards, + Preservation::Preserve, + )?; + } + + Ok(().into()) +} + +fn distribute_rewards_inner( + candidate: &Candidate, + rewards: T::Balance, +) -> Result> { + { + let candidate_auto_stake = AutoCompounding::::computed_stake(candidate, candidate)?.0; + dbg!(candidate_auto_stake) + }; + + // `RewardsCollatorCommission` is a `Perbill` so we're not worried about overflow. + let candidate_rewards = T::RewardsCollatorCommission::get() * rewards; + let delegators_rewards = rewards.err_sub(&candidate_rewards)?; + + let Stake(auto_total_stake) = AutoCompounding::::total_staked(candidate); + let Stake(manual_total_stake) = ManualRewards::::total_staked(candidate); + let combined_total_stake = auto_total_stake.err_add(&manual_total_stake)?; + + let candidate_manual_stake = if manual_total_stake.is_zero() { + Zero::zero() + } else { + ManualRewards::::computed_stake(candidate, candidate)?.0 + }; + + // Distribute delegators ManualRewards rewards, it implies some rounding. + let delegators_manual_rewards = if manual_total_stake.is_zero() { + Zero::zero() + } else { + let rewards = delegators_rewards.mul_div(manual_total_stake, combined_total_stake)?; + ManualRewards::::increase_rewards(&candidate, Stake(rewards))?.0 + }; + + // Distribute delegators AutoCompounding rewards with dust from ManualRewards. + // If there is no auto compounding stake but auto compounding rewards it + // means it comes from manual rewards rounding. Having non-zero total stake + // with 0 share supply will cause issues, so in this case we distribute this + // dust as candidate manual rewards. + let delegators_auto_rewards = delegators_rewards.err_sub(&delegators_manual_rewards)?; + let delegators_auto_dust = if !auto_total_stake.is_zero() { + AutoCompounding::::share_stake_among_holders(candidate, Stake(delegators_auto_rewards))?; + Zero::zero() + } else { + delegators_auto_rewards + }; + + // Distribute candidate AutoCompounding rewards, it implies some rounding. + let candidate_auto_rewards = if auto_total_stake.is_zero() { + Zero::zero() + } else { + let candidate_auto_stake = AutoCompounding::::computed_stake(candidate, candidate)?.0; + let candidate_combined_stake = + dbg!(candidate_manual_stake).err_add(&candidate_auto_stake)?; + let rewards = candidate_rewards.mul_div(candidate_auto_stake, candidate_combined_stake)?; + let new_shares = AutoCompounding::::stake_to_shares(candidate, Stake(dbg!(rewards)))?; + + if dbg!(new_shares).0.is_zero() { + Zero::zero() + } else { + AutoCompounding::::add_shares(candidate, candidate, new_shares)?.0 + } + }; + + // Distribute candidate ManualRewards rewards with dust from AutoCompounding. + // The amount is returned by the function and will be transfered to the candidate account. + let candidate_manual_rewards = candidate_rewards + .err_sub(&candidate_auto_rewards)? + .err_add(&delegators_auto_dust)?; + + let additional_stake = delegators_auto_rewards.err_add(&candidate_auto_rewards)?; + Candidates::::add_total_stake(candidate, &Stake(additional_stake))?; + + Pallet::::deposit_event(Event::::RewardedCollator { + collator: candidate.clone(), + auto_compounding_rewards: candidate_auto_rewards, + manual_claim_rewards: candidate_manual_rewards, + }); + Pallet::::deposit_event(Event::::RewardedDelegators { + collator: candidate.clone(), + auto_compounding_rewards: delegators_auto_rewards, + manual_claim_rewards: delegators_manual_rewards, + }); + + Ok(candidate_manual_rewards) +} From 3d1793ea17da1d17485e1e179faa9ad66b38f332 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:05:36 +0200 Subject: [PATCH 03/10] tests (wip) --- pallets/pooled-staking/src/tests/mod.rs | 1 + pallets/pooled-staking/src/tests/rewards.rs | 178 ++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 pallets/pooled-staking/src/tests/rewards.rs diff --git a/pallets/pooled-staking/src/tests/mod.rs b/pallets/pooled-staking/src/tests/mod.rs index e9b19503b..0d85a15b4 100644 --- a/pallets/pooled-staking/src/tests/mod.rs +++ b/pallets/pooled-staking/src/tests/mod.rs @@ -18,6 +18,7 @@ mod candidates; mod delegator_flow; mod manual_rewards; mod rebalance; +mod rewards; use { crate::{ diff --git a/pallets/pooled-staking/src/tests/rewards.rs b/pallets/pooled-staking/src/tests/rewards.rs new file mode 100644 index 000000000..e90c73640 --- /dev/null +++ b/pallets/pooled-staking/src/tests/rewards.rs @@ -0,0 +1,178 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +use crate::pools::AutoCompounding; + +use { + super::*, + crate::{assert_eq_last_events, Pallet, TargetPool}, + tp_core::DistributeRewards, +}; + +struct Delegation { + candidate: AccountId, + delegator: AccountId, + pool: TargetPool, + stake: Balance, +} + +struct RewardRequest { + collator: AccountId, + rewards: Balance, +} + +struct ExpectedStake { + candidate: AccountId, + delegator: AccountId, + auto_stake: Balance, + auto_shares: Balance, + manual_stake: Balance, + manual_shares: Balance, + claimable_rewards: Balance, +} + +struct ExpectedDistribution { + collator_auto: Balance, + collator_manual: Balance, + delegators_auto: Balance, + delegators_manual: Balance, +} + +fn test_distribution( + delegations: &[Delegation], + reward: RewardRequest, + stakes: &[ExpectedStake], + distribution: ExpectedDistribution, +) { + use crate::traits::Timer; + let block_number = ::JoiningRequestTimer::now(); + + for d in delegations { + assert_ok!(Staking::request_delegate( + RuntimeOrigin::signed(d.delegator), + d.candidate, + d.pool, + d.stake, + )); + } + + for _ in 0..BLOCKS_TO_WAIT { + roll_one_block(); + } + + for d in delegations { + assert_ok!(Staking::execute_pending_operations( + RuntimeOrigin::signed(d.delegator), + vec![PendingOperationQuery { + delegator: d.delegator, + operation: match d.pool { + TargetPool::AutoCompounding => PendingOperationKey::JoiningAutoCompounding { + candidate: d.candidate, at: block_number + }, + TargetPool::ManualRewards => PendingOperationKey::JoiningManualRewards { + candidate: d.candidate, at: block_number + }, + } + }] + )); + } + + assert_ok!(Pallet::::distribute_rewards( + reward.collator, + reward.rewards + )); + + assert_eq_last_events!(vec![ + Event::::RewardedCollator { + collator: reward.collator, + auto_compounding_rewards: distribution.collator_auto, + manual_claim_rewards: distribution.collator_manual, + }, + Event::RewardedDelegators { + collator: reward.collator, + auto_compounding_rewards: distribution.delegators_auto, + manual_claim_rewards: distribution.delegators_manual, + }, + ]); + + // TODO: Test new stake/shares quantities +} + +// #[test] +// candidate_only_manual_only() { +// ExtBuilder::default().build().execute_with(|| { +// test_distribution(&[ +// Delegation { +// candidate: ACCOUNT_CANDIDATE_1, +// delegator: ACCOUNT_CANDIDATE_1, +// pool: TargetPool::ManualRewards, +// stake: 1_000_000_000 +// } +// ], RewardRequest { +// collator: ACCOUNT_CANDIDATE_1, +// }, stakes, distribution) +// }); +// } + +// pool_test!( +// fn candidate_only_single_pool

() { +// ExtBuilder::default().build().execute_with(|| { +// let share_value = InitialManualClaimShareValue::get(); +// let stake = 1_000 * share_value; +// let rewards = 200 * share_value + 42; + +// FullDelegation { +// candidate: ACCOUNT_CANDIDATE_1, +// delegator: ACCOUNT_CANDIDATE_1, +// request_amount: stake, +// expected_increase: stake, +// ..default() +// } +// .test::

(); + +// assert_ok!(Pallet::::distribute_rewards( +// ACCOUNT_CANDIDATE_1, +// rewards +// )); + +// let rewards_collator = rewards * 2 / 10; // 20% +// let rewards_delegators = rewards - rewards_collator; + +// let ( +// rewards_collator_manual, +// rewards_collator_auto, +// rewards_delegators_manual, +// rewards_delegators_auto, +// ) = match P::target_pool() { +// TargetPool::AutoCompounding => (0, rewards_collator, 0, rewards_delegators), +// TargetPool::ManualRewards => (rewards_collator, 0, rewards_delegators, 0), +// }; + +// assert_eq_last_events!(vec![ +// Event::::RewardedCollator { +// collator: ACCOUNT_CANDIDATE_1, +// auto_compounding_rewards: rewards_collator_auto, +// manual_claim_rewards: rewards_collator_manual, +// }, +// Event::RewardedDelegators { +// collator: ACCOUNT_CANDIDATE_1, +// auto_compounding_rewards: rewards_delegators_auto, +// manual_claim_rewards: rewards_delegators_manual, +// }, +// ]) +// }) +// } +// ); From 9fb4f6634892be3e2e4c0fdb0c089d907f3ac4cb Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:06:56 +0200 Subject: [PATCH 04/10] reward distribution tests --- pallets/pooled-staking/src/pools.rs | 15 +- pallets/pooled-staking/src/tests/rewards.rs | 452 ++++++++++++++++---- 2 files changed, 383 insertions(+), 84 deletions(-) diff --git a/pallets/pooled-staking/src/pools.rs b/pallets/pooled-staking/src/pools.rs index 3271db3d8..e81985884 100644 --- a/pallets/pooled-staking/src/pools.rs +++ b/pallets/pooled-staking/src/pools.rs @@ -479,7 +479,7 @@ fn distribute_rewards_inner( ) -> Result> { { let candidate_auto_stake = AutoCompounding::::computed_stake(candidate, candidate)?.0; - dbg!(candidate_auto_stake) + candidate_auto_stake }; // `RewardsCollatorCommission` is a `Perbill` so we're not worried about overflow. @@ -510,11 +510,11 @@ fn distribute_rewards_inner( // with 0 share supply will cause issues, so in this case we distribute this // dust as candidate manual rewards. let delegators_auto_rewards = delegators_rewards.err_sub(&delegators_manual_rewards)?; - let delegators_auto_dust = if !auto_total_stake.is_zero() { + let (delegators_auto_rewards, delegators_auto_dust) = if !auto_total_stake.is_zero() { AutoCompounding::::share_stake_among_holders(candidate, Stake(delegators_auto_rewards))?; - Zero::zero() + (delegators_auto_rewards, Zero::zero()) } else { - delegators_auto_rewards + (Zero::zero(), delegators_auto_rewards) }; // Distribute candidate AutoCompounding rewards, it implies some rounding. @@ -522,12 +522,11 @@ fn distribute_rewards_inner( Zero::zero() } else { let candidate_auto_stake = AutoCompounding::::computed_stake(candidate, candidate)?.0; - let candidate_combined_stake = - dbg!(candidate_manual_stake).err_add(&candidate_auto_stake)?; + let candidate_combined_stake = candidate_manual_stake.err_add(&candidate_auto_stake)?; let rewards = candidate_rewards.mul_div(candidate_auto_stake, candidate_combined_stake)?; - let new_shares = AutoCompounding::::stake_to_shares(candidate, Stake(dbg!(rewards)))?; + let new_shares = AutoCompounding::::stake_to_shares(candidate, Stake(rewards))?; - if dbg!(new_shares).0.is_zero() { + if new_shares.0.is_zero() { Zero::zero() } else { AutoCompounding::::add_shares(candidate, candidate, new_shares)?.0 diff --git a/pallets/pooled-staking/src/tests/rewards.rs b/pallets/pooled-staking/src/tests/rewards.rs index e90c73640..09657ab06 100644 --- a/pallets/pooled-staking/src/tests/rewards.rs +++ b/pallets/pooled-staking/src/tests/rewards.rs @@ -14,11 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Tanssi. If not, see -use crate::pools::AutoCompounding; - use { super::*, - crate::{assert_eq_last_events, Pallet, TargetPool}, + crate::{ + assert_eq_last_events, + pools::{AutoCompounding, ManualRewards}, + Pallet, TargetPool, + }, tp_core::DistributeRewards, }; @@ -34,17 +36,19 @@ struct RewardRequest { rewards: Balance, } -struct ExpectedStake { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct DelegatorState { candidate: AccountId, delegator: AccountId, auto_stake: Balance, auto_shares: Balance, manual_stake: Balance, manual_shares: Balance, - claimable_rewards: Balance, + pending_rewards: Balance, } -struct ExpectedDistribution { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Distribution { collator_auto: Balance, collator_manual: Balance, delegators_auto: Balance, @@ -54,8 +58,8 @@ struct ExpectedDistribution { fn test_distribution( delegations: &[Delegation], reward: RewardRequest, - stakes: &[ExpectedStake], - distribution: ExpectedDistribution, + stakes: &[DelegatorState], + distribution: Distribution, ) { use crate::traits::Timer; let block_number = ::JoiningRequestTimer::now(); @@ -80,21 +84,27 @@ fn test_distribution( delegator: d.delegator, operation: match d.pool { TargetPool::AutoCompounding => PendingOperationKey::JoiningAutoCompounding { - candidate: d.candidate, at: block_number + candidate: d.candidate, + at: block_number }, TargetPool::ManualRewards => PendingOperationKey::JoiningManualRewards { - candidate: d.candidate, at: block_number + candidate: d.candidate, + at: block_number }, } }] )); } + let candidate_balance_before = total_balance(&ACCOUNT_CANDIDATE_1); + assert_ok!(Pallet::::distribute_rewards( reward.collator, reward.rewards )); + let candidate_balance_after = total_balance(&ACCOUNT_CANDIDATE_1); + assert_eq_last_events!(vec![ Event::::RewardedCollator { collator: reward.collator, @@ -108,71 +118,361 @@ fn test_distribution( }, ]); - // TODO: Test new stake/shares quantities + for expected in stakes { + let actual = DelegatorState { + candidate: expected.candidate, + delegator: expected.delegator, + auto_stake: AutoCompounding::::computed_stake( + &expected.candidate, + &expected.delegator, + ) + .expect("to have stake") + .0, + auto_shares: AutoCompounding::::shares( + &expected.candidate, + &expected.delegator, + ) + .0, + manual_stake: ManualRewards::::computed_stake( + &expected.candidate, + &expected.delegator, + ) + .expect("to have stake") + .0, + manual_shares: ManualRewards::::shares( + &expected.candidate, + &expected.delegator, + ) + .0, + pending_rewards: ManualRewards::::pending_rewards( + &expected.candidate, + &expected.delegator, + ) + .expect("no overflow") + .0, + }; + + similar_asserts::assert_eq!(&actual, expected); + } + + assert_eq!( + distribution.collator_auto + + distribution.collator_manual + + distribution.delegators_auto + + distribution.delegators_manual, + reward.rewards, + "Distribution total doesn't match requested reward" + ); + + assert_eq!( + candidate_balance_before + distribution.collator_manual, + candidate_balance_after, + "candidate balance should be increased by collator_manual" + ); + + let sum_manual: Balance = stakes.iter().map(|s| s.pending_rewards).sum(); + assert_eq!( + sum_manual, distribution.delegators_manual, + "sum of pending rewards should match distributed delegators manual rewards" + ); + + let sum_auto_stake_before: Balance = delegations + .iter() + .filter_map(|d| match d.pool { + TargetPool::AutoCompounding => Some(d.stake), + _ => None, + }) + .sum(); + + let sum_auto_stake_after: Balance = stakes.iter().map(|s| s.auto_stake).sum(); + assert_eq!( + sum_auto_stake_after - sum_auto_stake_before, + distribution.collator_auto + distribution.delegators_auto, + "diff between sum of auto stake before and after distribution should match distributed auto rewards" + ); } -// #[test] -// candidate_only_manual_only() { -// ExtBuilder::default().build().execute_with(|| { -// test_distribution(&[ -// Delegation { -// candidate: ACCOUNT_CANDIDATE_1, -// delegator: ACCOUNT_CANDIDATE_1, -// pool: TargetPool::ManualRewards, -// stake: 1_000_000_000 -// } -// ], RewardRequest { -// collator: ACCOUNT_CANDIDATE_1, -// }, stakes, distribution) -// }); -// } - -// pool_test!( -// fn candidate_only_single_pool

() { -// ExtBuilder::default().build().execute_with(|| { -// let share_value = InitialManualClaimShareValue::get(); -// let stake = 1_000 * share_value; -// let rewards = 200 * share_value + 42; - -// FullDelegation { -// candidate: ACCOUNT_CANDIDATE_1, -// delegator: ACCOUNT_CANDIDATE_1, -// request_amount: stake, -// expected_increase: stake, -// ..default() -// } -// .test::

(); - -// assert_ok!(Pallet::::distribute_rewards( -// ACCOUNT_CANDIDATE_1, -// rewards -// )); - -// let rewards_collator = rewards * 2 / 10; // 20% -// let rewards_delegators = rewards - rewards_collator; - -// let ( -// rewards_collator_manual, -// rewards_collator_auto, -// rewards_delegators_manual, -// rewards_delegators_auto, -// ) = match P::target_pool() { -// TargetPool::AutoCompounding => (0, rewards_collator, 0, rewards_delegators), -// TargetPool::ManualRewards => (rewards_collator, 0, rewards_delegators, 0), -// }; - -// assert_eq_last_events!(vec![ -// Event::::RewardedCollator { -// collator: ACCOUNT_CANDIDATE_1, -// auto_compounding_rewards: rewards_collator_auto, -// manual_claim_rewards: rewards_collator_manual, -// }, -// Event::RewardedDelegators { -// collator: ACCOUNT_CANDIDATE_1, -// auto_compounding_rewards: rewards_delegators_auto, -// manual_claim_rewards: rewards_delegators_manual, -// }, -// ]) -// }) -// } -// ); +#[test] +fn candidate_only_manual_only() { + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::ManualRewards, + stake: 1_000_000_000, + }], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 1_000_000, + }, + &[DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + auto_shares: 0, + auto_stake: 0, + manual_shares: 1_000, + manual_stake: 1_000_000_000, + pending_rewards: 800_000, + }], + Distribution { + collator_auto: 0, + collator_manual: 200_000, // 20% of rewards + delegators_auto: 0, + delegators_manual: 800_000, // 80% of rewards + }, + ) + }); +} + +#[test] +fn candidate_only_auto_only() { + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::AutoCompounding, + stake: 1_000_000_000, + }], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 10_000_000, + }, + &[DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + auto_shares: 1_001, + // initial auto stake is 1_000_000_000 for + // 8_000_000 is shared between all delegators, so 1 share + // is now worth 1_008_000_000 / 1000 = 1_008_000 now + // collator is should be rewarded 2_000_000 in auto shares, + // which allows to get 1 more share, so the collator now + // have 1_001 shares worth + // 1_008_000_000 + 1_008_000 = 1_009_008_000 + auto_stake: 1_009_008_000, + manual_shares: 0, + manual_stake: 0, + pending_rewards: 0, + }], + Distribution { + // 20% of rewards, rounded down to closest amount of Auto shares + // AFTER delegators rewards has been rewarded + collator_auto: 1_008_000, + // dust from collator_auto + collator_manual: 992_000, + delegators_auto: 8_000_000, // 80% of rewards + delegators_manual: 0, + }, + ) + }); +} + +#[test] +fn candidate_only_mixed() { + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[ + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::AutoCompounding, + stake: 1_000_000_000, + }, + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::ManualRewards, + stake: 250_000_000, + }, + ], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 10_000_000, + }, + &[DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + auto_shares: 1_001, + auto_stake: 1_007_406_400, + manual_shares: 250, + manual_stake: 250_000_000, + pending_rewards: 1_600_000, + }], + Distribution { + collator_auto: 1_006_400, + collator_manual: 993_600, + delegators_auto: 6_400_000, + delegators_manual: 1_600_000, + }, + ) + }); +} + +#[test] +fn delegators_manual_only() { + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[ + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::ManualRewards, + stake: 1_000_000_000, + }, + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + pool: TargetPool::ManualRewards, + stake: 250_000_000, + }, + ], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 10_000_000, + }, + &[ + DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + auto_shares: 0, + auto_stake: 0, + manual_shares: 1_000, + manual_stake: 1_000_000_000, + pending_rewards: 6_400_000, + }, + DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + auto_shares: 0, + auto_stake: 0, + manual_shares: 250, + manual_stake: 250_000_000, + pending_rewards: 1_600_000, + }, + ], + Distribution { + collator_auto: 0, + collator_manual: 2_000_000, + delegators_auto: 0, + delegators_manual: 8_000_000, + }, + ) + }); +} + +#[test] +fn delegators_auto_only() { + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[ + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::AutoCompounding, + stake: 1_000_000_000, + }, + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + pool: TargetPool::AutoCompounding, + stake: 250_000_000, + }, + ], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 10_000_000, + }, + &[ + DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + auto_shares: 1_001, + auto_stake: 1_007_406_400, + manual_shares: 0, + manual_stake: 0, + pending_rewards: 0, + }, + DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + auto_shares: 250, + auto_stake: 251_600_000, + manual_shares: 0, + manual_stake: 0, + pending_rewards: 0, + }, + ], + Distribution { + collator_auto: 1_006_400, + collator_manual: 993_600, + delegators_auto: 8_000_000, + delegators_manual: 0, + }, + ) + }); +} + +#[test] +fn delegators_mixed() { + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[ + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::AutoCompounding, + stake: 1_000_000_000, + }, + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + pool: TargetPool::ManualRewards, + stake: 500_000_000, + }, + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + pool: TargetPool::ManualRewards, + stake: 250_000_000, + }, + Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + pool: TargetPool::AutoCompounding, + stake: 500_000_000, + }, + ], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 10_000_000, + }, + &[ + DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_CANDIDATE_1, + auto_shares: 1_001, + auto_stake: 1_004_559_388, + manual_shares: 500, + manual_stake: 500_000_000, + pending_rewards: 1_777_500, + }, + DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + auto_shares: 500, + auto_stake: 501_777_916, + manual_shares: 250, + manual_stake: 250_000_000, + pending_rewards: 888_750, + }, + ], + Distribution { + collator_auto: 1_003_555, + collator_manual: 996_445, + delegators_auto: 5_333_750, + delegators_manual: 2_666_250, + }, + ); + }); +} From 36583d35c98d8532d5c99fc72a4ccb13449fd188 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:17:18 +0200 Subject: [PATCH 05/10] fix test + remove warning --- pallets/pooled-staking/src/pools.rs | 1 + pallets/pooled-staking/src/tests/rewards.rs | 27 ++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pallets/pooled-staking/src/pools.rs b/pallets/pooled-staking/src/pools.rs index e81985884..565c1b91e 100644 --- a/pallets/pooled-staking/src/pools.rs +++ b/pallets/pooled-staking/src/pools.rs @@ -342,6 +342,7 @@ impl_pool!( ); impl ManualRewards { + #[allow(dead_code)] pub fn pending_rewards( candidate: &Candidate, delegator: &Delegator, diff --git a/pallets/pooled-staking/src/tests/rewards.rs b/pallets/pooled-staking/src/tests/rewards.rs index 09657ab06..7dfaa610a 100644 --- a/pallets/pooled-staking/src/tests/rewards.rs +++ b/pallets/pooled-staking/src/tests/rewards.rs @@ -64,6 +64,7 @@ fn test_distribution( use crate::traits::Timer; let block_number = ::JoiningRequestTimer::now(); + // Request all delegations for d in delegations { assert_ok!(Staking::request_delegate( RuntimeOrigin::signed(d.delegator), @@ -73,10 +74,12 @@ fn test_distribution( )); } + // Wait for delegation to be executable for _ in 0..BLOCKS_TO_WAIT { roll_one_block(); } + // Execute delegations for d in delegations { assert_ok!(Staking::execute_pending_operations( RuntimeOrigin::signed(d.delegator), @@ -96,15 +99,15 @@ fn test_distribution( )); } + // Distribute rewards let candidate_balance_before = total_balance(&ACCOUNT_CANDIDATE_1); - assert_ok!(Pallet::::distribute_rewards( reward.collator, reward.rewards )); - let candidate_balance_after = total_balance(&ACCOUNT_CANDIDATE_1); + // Check events matches the expected distribution. assert_eq_last_events!(vec![ Event::::RewardedCollator { collator: reward.collator, @@ -118,6 +121,7 @@ fn test_distribution( }, ]); + // Check the state of each delegate match the expected values. for expected in stakes { let actual = DelegatorState { candidate: expected.candidate, @@ -155,6 +159,7 @@ fn test_distribution( similar_asserts::assert_eq!(&actual, expected); } + // Additional checks. assert_eq!( distribution.collator_auto + distribution.collator_manual @@ -184,7 +189,7 @@ fn test_distribution( }) .sum(); - let sum_auto_stake_after: Balance = stakes.iter().map(|s| s.auto_stake).sum(); + let sum_auto_stake_after = AutoCompounding::::total_staked(&reward.collator).0; assert_eq!( sum_auto_stake_after - sum_auto_stake_before, distribution.collator_auto + distribution.delegators_auto, @@ -470,8 +475,22 @@ fn delegators_mixed() { Distribution { collator_auto: 1_003_555, collator_manual: 996_445, - delegators_auto: 5_333_750, + // Total stake: 2_250_000_000 + // Auto stake: 1_500_000_000 + // Manual stake: 750_000_000 + // Manual shares: 750 + // Rewards towards delegators: 80% of 10_000_000 = 8_000_000 + // Rewards towards manual deleg + // = 8_000_000 * 750_000_000 / 2_250_000_000 + // = 2_666_666 + // => 2_666_250 (rounding down to closest multiple of 750) + // gives dust of 2_666_666 - 2_666_250 = 416 delegators_manual: 2_666_250, + // Rewards towards auto deleg + // = Rewards deleg - Rewards manual deleg + // = 8_000_000 - 2_666_250 + // = 5_333_750 + delegators_auto: 5_333_750, }, ); }); From ef7207452ecd54ab46145c2c727251afa5ad0808 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:20:25 +0200 Subject: [PATCH 06/10] remove dead code Co-authored-by: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com> --- pallets/pooled-staking/src/pools.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pallets/pooled-staking/src/pools.rs b/pallets/pooled-staking/src/pools.rs index 565c1b91e..c1425e293 100644 --- a/pallets/pooled-staking/src/pools.rs +++ b/pallets/pooled-staking/src/pools.rs @@ -478,10 +478,6 @@ fn distribute_rewards_inner( candidate: &Candidate, rewards: T::Balance, ) -> Result> { - { - let candidate_auto_stake = AutoCompounding::::computed_stake(candidate, candidate)?.0; - candidate_auto_stake - }; // `RewardsCollatorCommission` is a `Perbill` so we're not worried about overflow. let candidate_rewards = T::RewardsCollatorCommission::get() * rewards; From 5990c78a2f0146220b9b55bd5867a03ce7bc6e24 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 2 Oct 2023 15:28:45 +0200 Subject: [PATCH 07/10] wip bench --- pallets/pooled-staking/src/benchmarking.rs | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/pallets/pooled-staking/src/benchmarking.rs b/pallets/pooled-staking/src/benchmarking.rs index 3a97b3ddf..e78b2f95b 100644 --- a/pallets/pooled-staking/src/benchmarking.rs +++ b/pallets/pooled-staking/src/benchmarking.rs @@ -525,6 +525,60 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn distribute_rewards() -> Result<(), BenchmarkError> { + const USER_SEED: u32 = 1; + + let source_stake = min_candidate_stk::() * 10u32.into(); + + let (caller, _deposit_amount) = create_funded_user::("caller", USER_SEED, source_stake * 2u32.into()); + + T::EligibleCandidatesFilter::make_candidate_eligible(&caller, true); + + PooledStaking::::request_delegate( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + TargetPool::AutoCompounding, + source_stake, + )?; + PooledStaking::::request_delegate( + RawOrigin::Signed(caller.clone()).into(), + caller.clone(), + TargetPool::ManualRewards, + source_stake, + )?; + + let timer = T::JoiningRequestTimer::now(); + + T::JoiningRequestTimer::skip_to_elapsed(); + + PooledStaking::::execute_pending_operations( + RawOrigin::Signed(caller.clone()).into(), + vec![PendingOperationQuery { + delegator: caller.clone(), + operation: JoiningAutoCompounding { + candidate: caller.clone(), + at: timer.clone(), + }, + }, PendingOperationQuery { + delegator: caller.clone(), + operation: JoiningManualRewards { + candidate: caller.clone(), + at: timer.clone(), + }, + }], + )?; + + T::Currency::mint_into(&caller, source_stake).unwrap(); + + #[block] + { + frame_support::assert_ok!(crate::pools::distribute_rewards::(&caller, source_stake)); + } + + Ok(()) + } + impl_benchmark_test_suite!( PooledStaking, crate::mock::ExtBuilder::default().build(), From 0c8de0497a6bbb1d030d635fb50699c3d35a5979 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:22:52 +0200 Subject: [PATCH 08/10] fix and test edge cases --- pallets/pooled-staking/src/pools.rs | 25 ++++--- pallets/pooled-staking/src/tests/rewards.rs | 82 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/pallets/pooled-staking/src/pools.rs b/pallets/pooled-staking/src/pools.rs index c1425e293..a358db954 100644 --- a/pallets/pooled-staking/src/pools.rs +++ b/pallets/pooled-staking/src/pools.rs @@ -455,7 +455,6 @@ impl ManualRewards { /// AutoCompounding shares. This can lead to some rounding, which will be /// absorbed in the ManualRewards distribution, which simply consist of /// transfering the funds to the candidate account. -#[allow(dead_code)] pub fn distribute_rewards( candidate: &Candidate, rewards: T::Balance, @@ -478,7 +477,6 @@ fn distribute_rewards_inner( candidate: &Candidate, rewards: T::Balance, ) -> Result> { - // `RewardsCollatorCommission` is a `Perbill` so we're not worried about overflow. let candidate_rewards = T::RewardsCollatorCommission::get() * rewards; let delegators_rewards = rewards.err_sub(&candidate_rewards)?; @@ -518,14 +516,23 @@ fn distribute_rewards_inner( let candidate_auto_rewards = if auto_total_stake.is_zero() { Zero::zero() } else { - let candidate_auto_stake = AutoCompounding::::computed_stake(candidate, candidate)?.0; - let candidate_combined_stake = candidate_manual_stake.err_add(&candidate_auto_stake)?; - let rewards = candidate_rewards.mul_div(candidate_auto_stake, candidate_combined_stake)?; - let new_shares = AutoCompounding::::stake_to_shares(candidate, Stake(rewards))?; + 'a: { + let candidate_auto_stake = + AutoCompounding::::computed_stake(candidate, candidate)?.0; + let candidate_combined_stake = candidate_manual_stake.err_add(&candidate_auto_stake)?; + + if candidate_combined_stake.is_zero() { + break 'a Zero::zero(); + } + + let rewards = + candidate_rewards.mul_div(candidate_auto_stake, candidate_combined_stake)?; + let new_shares = AutoCompounding::::stake_to_shares(candidate, Stake(rewards))?; + + if new_shares.0.is_zero() { + break 'a Zero::zero(); + } - if new_shares.0.is_zero() { - Zero::zero() - } else { AutoCompounding::::add_shares(candidate, candidate, new_shares)?.0 } }; diff --git a/pallets/pooled-staking/src/tests/rewards.rs b/pallets/pooled-staking/src/tests/rewards.rs index 7dfaa610a..bfa2ad59f 100644 --- a/pallets/pooled-staking/src/tests/rewards.rs +++ b/pallets/pooled-staking/src/tests/rewards.rs @@ -495,3 +495,85 @@ fn delegators_mixed() { ); }); } + +#[test] +fn candidate_only_no_stake() { + // Rewarding a candidate that does not have any stake works + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 1_000_000, + }, + &[], + Distribution { + collator_auto: 0, + collator_manual: 1_000_000, // 100% of rewards + delegators_auto: 0, + delegators_manual: 0, // 0% of rewards + }, + ) + }); +} + +#[test] +fn delegator_only_candidate_zero() { + // Rewarding a candidate that does not have any stake works + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + pool: TargetPool::ManualRewards, + stake: 250_000_000, + }], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 1_000_000, + }, + &[DelegatorState { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + auto_shares: 0, + auto_stake: 0, + manual_shares: 250, + manual_stake: 250_000_000, + pending_rewards: 800_000, + }], + Distribution { + collator_auto: 0, + collator_manual: 200_000, // 20% of rewards + delegators_auto: 0, + delegators_manual: 800_000, // 80% of rewards + }, + ) + }); +} + +#[test] +fn delegator_only_candidate_no_stake_auto_compounding() { + // Rewarding a candidate that does not have any stake, while some delegator + // has stake for that candidate + ExtBuilder::default().build().execute_with(|| { + test_distribution( + &[Delegation { + candidate: ACCOUNT_CANDIDATE_1, + delegator: ACCOUNT_DELEGATOR_1, + pool: TargetPool::AutoCompounding, + stake: 250_000_000, + }], + RewardRequest { + collator: ACCOUNT_CANDIDATE_1, + rewards: 1_000_000, + }, + &[], + Distribution { + collator_auto: 0, + collator_manual: 200_000, // 20% of rewards + delegators_auto: 800_000, // 80% of rewards + delegators_manual: 0, + }, + ) + }); +} From 77d1df30b31df56d523a4c401b21024ac08f6142 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:25:08 +0200 Subject: [PATCH 09/10] run bench --- pallets/pooled-staking/src/benchmarking.rs | 32 +++--- pallets/pooled-staking/src/weights.rs | 117 +++++++++++++-------- 2 files changed, 94 insertions(+), 55 deletions(-) diff --git a/pallets/pooled-staking/src/benchmarking.rs b/pallets/pooled-staking/src/benchmarking.rs index e78b2f95b..74cb15317 100644 --- a/pallets/pooled-staking/src/benchmarking.rs +++ b/pallets/pooled-staking/src/benchmarking.rs @@ -531,7 +531,8 @@ mod benchmarks { let source_stake = min_candidate_stk::() * 10u32.into(); - let (caller, _deposit_amount) = create_funded_user::("caller", USER_SEED, source_stake * 2u32.into()); + let (caller, _deposit_amount) = + create_funded_user::("caller", USER_SEED, source_stake * 2u32.into()); T::EligibleCandidatesFilter::make_candidate_eligible(&caller, true); @@ -554,26 +555,29 @@ mod benchmarks { PooledStaking::::execute_pending_operations( RawOrigin::Signed(caller.clone()).into(), - vec![PendingOperationQuery { - delegator: caller.clone(), - operation: JoiningAutoCompounding { - candidate: caller.clone(), - at: timer.clone(), + vec![ + PendingOperationQuery { + delegator: caller.clone(), + operation: JoiningAutoCompounding { + candidate: caller.clone(), + at: timer.clone(), + }, }, - }, PendingOperationQuery { - delegator: caller.clone(), - operation: JoiningManualRewards { - candidate: caller.clone(), - at: timer.clone(), + PendingOperationQuery { + delegator: caller.clone(), + operation: JoiningManualRewards { + candidate: caller.clone(), + at: timer.clone(), + }, }, - }], + ], )?; - T::Currency::mint_into(&caller, source_stake).unwrap(); + T::Currency::mint_into(&T::StakingAccount::get(), source_stake).unwrap(); #[block] { - frame_support::assert_ok!(crate::pools::distribute_rewards::(&caller, source_stake)); + crate::pools::distribute_rewards::(&caller, source_stake)?; } Ok(()) diff --git a/pallets/pooled-staking/src/weights.rs b/pallets/pooled-staking/src/weights.rs index df5d16d6e..5af329ca1 100644 --- a/pallets/pooled-staking/src/weights.rs +++ b/pallets/pooled-staking/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_pooled_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `pop-os`, CPU: `12th Gen Intel(R) Core(TM) i7-1260P` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -59,6 +59,7 @@ pub trait WeightInfo { fn rebalance_hold() -> Weight; fn update_candidate_position(b: u32, ) -> Weight; fn swap_pool() -> Weight; + fn distribute_rewards() -> Weight; } /// Weights for pallet_pooled_staking using the Substrate node and recommended hardware. @@ -82,8 +83,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1321` // Estimated: `29536` - // Minimum execution time: 148_140_000 picoseconds. - Weight::from_parts(196_694_000, 29536) + // Minimum execution time: 127_339_000 picoseconds. + Weight::from_parts(133_146_000, 29536) .saturating_add(T::DbWeight::get().reads(17_u64)) .saturating_add(T::DbWeight::get().writes(9_u64)) } @@ -102,10 +103,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `543 + b * (390 ±0)` // Estimated: `3593 + b * (25141 ±0)` - // Minimum execution time: 97_115_000 picoseconds. - Weight::from_parts(103_358_000, 3593) - // Standard Error: 243_606 - .saturating_add(Weight::from_parts(97_506_690, 0).saturating_mul(b.into())) + // Minimum execution time: 89_544_000 picoseconds. + Weight::from_parts(91_417_000, 3593) + // Standard Error: 630_031 + .saturating_add(Weight::from_parts(99_103_944, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((11_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -124,8 +125,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `724` // Estimated: `33889` - // Minimum execution time: 125_481_000 picoseconds. - Weight::from_parts(142_689_000, 33889) + // Minimum execution time: 111_997_000 picoseconds. + Weight::from_parts(124_683_000, 33889) .saturating_add(T::DbWeight::get().reads(16_u64)) .saturating_add(T::DbWeight::get().writes(11_u64)) } @@ -138,10 +139,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `360 + b * (456 ±0)` // Estimated: `6196 + b * (7882 ±0)` - // Minimum execution time: 67_358_000 picoseconds. - Weight::from_parts(62_976_252, 6196) - // Standard Error: 364_479 - .saturating_add(Weight::from_parts(51_801_209, 0).saturating_mul(b.into())) + // Minimum execution time: 57_580_000 picoseconds. + Weight::from_parts(60_814_000, 6196) + // Standard Error: 421_370 + .saturating_add(Weight::from_parts(55_273_020, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -158,8 +159,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `980` // Estimated: `11870` - // Minimum execution time: 94_217_000 picoseconds. - Weight::from_parts(109_367_000, 11870) + // Minimum execution time: 98_014_000 picoseconds. + Weight::from_parts(128_615_000, 11870) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -174,10 +175,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `399 + b * (356 ±0)` // Estimated: `1881 + b * (15206 ±0)` - // Minimum execution time: 43_231_000 picoseconds. - Weight::from_parts(56_401_000, 1881) - // Standard Error: 91_302 - .saturating_add(Weight::from_parts(35_820_280, 0).saturating_mul(b.into())) + // Minimum execution time: 46_082_000 picoseconds. + Weight::from_parts(60_293_000, 1881) + // Standard Error: 131_937 + .saturating_add(Weight::from_parts(35_500_124, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) @@ -190,11 +191,28 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `478` // Estimated: `31168` - // Minimum execution time: 87_575_000 picoseconds. - Weight::from_parts(91_480_000, 31168) + // Minimum execution time: 80_829_000 picoseconds. + Weight::from_parts(97_569_000, 31168) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } + /// Storage: PooledStaking Pools (r:9 w:5) + /// Proof Skipped: PooledStaking Pools (max_values: None, max_size: None, mode: Measured) + /// Storage: PooledStaking SortedEligibleCandidates (r:1 w:1) + /// Proof Skipped: PooledStaking SortedEligibleCandidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Session NextKeys (r:1 w:0) + /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn distribute_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1302` + // Estimated: `24567` + // Minimum execution time: 151_254_000 picoseconds. + Weight::from_parts(178_410_000, 24567) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } } // For backwards compatibility and tests @@ -217,8 +235,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1321` // Estimated: `29536` - // Minimum execution time: 148_140_000 picoseconds. - Weight::from_parts(196_694_000, 29536) + // Minimum execution time: 127_339_000 picoseconds. + Weight::from_parts(133_146_000, 29536) .saturating_add(RocksDbWeight::get().reads(17_u64)) .saturating_add(RocksDbWeight::get().writes(9_u64)) } @@ -237,10 +255,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `543 + b * (390 ±0)` // Estimated: `3593 + b * (25141 ±0)` - // Minimum execution time: 97_115_000 picoseconds. - Weight::from_parts(103_358_000, 3593) - // Standard Error: 243_606 - .saturating_add(Weight::from_parts(97_506_690, 0).saturating_mul(b.into())) + // Minimum execution time: 89_544_000 picoseconds. + Weight::from_parts(91_417_000, 3593) + // Standard Error: 630_031 + .saturating_add(Weight::from_parts(99_103_944, 0).saturating_mul(b.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((11_u64).saturating_mul(b.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -259,8 +277,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `724` // Estimated: `33889` - // Minimum execution time: 125_481_000 picoseconds. - Weight::from_parts(142_689_000, 33889) + // Minimum execution time: 111_997_000 picoseconds. + Weight::from_parts(124_683_000, 33889) .saturating_add(RocksDbWeight::get().reads(16_u64)) .saturating_add(RocksDbWeight::get().writes(11_u64)) } @@ -273,10 +291,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `360 + b * (456 ±0)` // Estimated: `6196 + b * (7882 ±0)` - // Minimum execution time: 67_358_000 picoseconds. - Weight::from_parts(62_976_252, 6196) - // Standard Error: 364_479 - .saturating_add(Weight::from_parts(51_801_209, 0).saturating_mul(b.into())) + // Minimum execution time: 57_580_000 picoseconds. + Weight::from_parts(60_814_000, 6196) + // Standard Error: 421_370 + .saturating_add(Weight::from_parts(55_273_020, 0).saturating_mul(b.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(b.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -293,8 +311,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `980` // Estimated: `11870` - // Minimum execution time: 94_217_000 picoseconds. - Weight::from_parts(109_367_000, 11870) + // Minimum execution time: 98_014_000 picoseconds. + Weight::from_parts(128_615_000, 11870) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -309,10 +327,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `399 + b * (356 ±0)` // Estimated: `1881 + b * (15206 ±0)` - // Minimum execution time: 43_231_000 picoseconds. - Weight::from_parts(56_401_000, 1881) - // Standard Error: 91_302 - .saturating_add(Weight::from_parts(35_820_280, 0).saturating_mul(b.into())) + // Minimum execution time: 46_082_000 picoseconds. + Weight::from_parts(60_293_000, 1881) + // Standard Error: 131_937 + .saturating_add(Weight::from_parts(35_500_124, 0).saturating_mul(b.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(b.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) @@ -325,9 +343,26 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `478` // Estimated: `31168` - // Minimum execution time: 87_575_000 picoseconds. - Weight::from_parts(91_480_000, 31168) + // Minimum execution time: 80_829_000 picoseconds. + Weight::from_parts(97_569_000, 31168) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } + /// Storage: PooledStaking Pools (r:9 w:5) + /// Proof Skipped: PooledStaking Pools (max_values: None, max_size: None, mode: Measured) + /// Storage: PooledStaking SortedEligibleCandidates (r:1 w:1) + /// Proof Skipped: PooledStaking SortedEligibleCandidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Session NextKeys (r:1 w:0) + /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn distribute_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1302` + // Estimated: `24567` + // Minimum execution time: 151_254_000 picoseconds. + Weight::from_parts(178_410_000, 24567) + .saturating_add(RocksDbWeight::get().reads(13_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } } From 1ce68e8863598daab13d34d1216a7976d42bc85c Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:20:00 +0200 Subject: [PATCH 10/10] trait docs --- primitives/core/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 76bb620e7..1a0b697f5 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -100,6 +100,7 @@ pub mod well_known_keys { &hex_literal::hex!["cec5070d609dd3497f72bde07fc96ba072763800a36a99fdfc7c10f6415f6ee6"]; } +/// Distribute rewards to an account. pub trait DistributeRewards { fn distribute_rewards(rewarded: AccountId, amount: Balance) -> DispatchResultWithPostInfo; }