diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 09f1746b8e5ba..f48a9b73bb728 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -358,6 +358,16 @@ enum AccountType { Reward, } +/// Indicates whether the the member account or the bonded accound is going +/// to receive the payout. +enum PayoutRecipient { + /// Stores the `AccountId` of the member account. + MemberAccount(T::AccountId), + /// Stores the `AccountId` of the bonded account and the member account. + /// (member_account, bonded_account) + BondedAccount(T::AccountId, T::AccountId), +} + /// A member in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] #[cfg_attr(feature = "std", derive(frame_support::PartialEqNoBound, DefaultNoBound))] @@ -857,9 +867,13 @@ impl BondedPool { /// Bond exactly `amount` from `who`'s funds into this pool. /// - /// If the bond type is `Create`, `StakingInterface::bond` is called, and `who` - /// is allowed to be killed. Otherwise, `StakingInterface::bond_extra` is called and `who` - /// cannot be killed. + /// If the bond type is `Create`, `StakingInterface::bond` is called, and + /// `who` is allowed to be killed. Otherwise, `StakingInterface::bond_extra` + /// is called and `who` cannot be killed. + /// + /// The `bond_from_member` argument is a flag that indicates whether the + /// `amount` should be transfered from `who`, or the `bonded_account` + /// already has the `amount`, but just needs to bond it. /// /// Returns `Ok(points_issues)`, `Err` otherwise. fn try_bond_funds( @@ -867,18 +881,22 @@ impl BondedPool { who: &T::AccountId, amount: BalanceOf, ty: BondType, + bond_from_member: bool, ) -> Result, DispatchError> { // Cache the value let bonded_account = self.bonded_account(); - T::Currency::transfer( - &who, - &bonded_account, - amount, - match ty { - BondType::Create => ExistenceRequirement::AllowDeath, - BondType::Later => ExistenceRequirement::KeepAlive, - }, - )?; + + if bond_from_member { + T::Currency::transfer( + &who, + &bonded_account, + amount, + match ty { + BondType::Create => ExistenceRequirement::AllowDeath, + BondType::Later => ExistenceRequirement::KeepAlive, + }, + )?; + } // We must calculate the points issued *before* we bond who's funds, else points:balance // ratio will be wrong. let points_issued = self.issue(amount); @@ -899,6 +917,36 @@ impl BondedPool { Ok(points_issued) } + /// Bond all of the pending_rewards from `member` into this pool. + /// + /// Transfers the member's rewards directly to the bonded account of the + /// member. Makes a call to the `StakingInterface::bond_extra` to bond the + /// newly claimed rewards. + /// + /// Emits the `PaidOut` event. + /// + /// Returns `Ok((points_issues, bonded))`, `Err` otherwise. + fn try_bond_funds_from_rewards( + &mut self, + who: &T::AccountId, + member: &mut PoolMember, + reward_pool: &mut RewardPool, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + let bonded_account = self.bonded_account(); + + let pending_rewards = Pallet::::calculate_and_payout_rewards( + member, + self, + reward_pool, + PayoutRecipient::::BondedAccount(who.clone(), bonded_account.clone()), + )?; + + let points_issued = + self.try_bond_funds(&bonded_account, pending_rewards, BondType::Later, false)?; + + Ok((points_issued, pending_rewards)) + } + // Set the state of `self`, and deposit an event if the state changed. State should never be set // directly in in order to ensure a state change event is always correctly deposited. fn set_state(&mut self, state: PoolState) { @@ -1340,8 +1388,13 @@ pub mod pallet { Created { depositor: T::AccountId, pool_id: PoolId }, /// A member has became bonded in a pool. Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf, joined: bool }, - /// A payout has been made to a member. - PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf }, + /// A payout has been made to a recipient. + PaidOut { + recipient: T::AccountId, + member: T::AccountId, + pool_id: PoolId, + payout: BalanceOf, + }, /// A member has unbonded from their pool. /// /// - `balance` is the corresponding balance of the number of points that has been @@ -1503,7 +1556,7 @@ pub mod pallet { reward_pool.update_records(pool_id, bonded_pool.points)?; bonded_pool.try_inc_members()?; - let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; + let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later, true)?; PoolMembers::insert( who.clone(), @@ -1532,13 +1585,13 @@ pub mod pallet { /// Bond `extra` more funds from `origin` into the pool to which they already belong. /// - /// Additional funds can come from either the free balance of the account, of from the + /// Additional funds can come from either the free balance of the account, or from the /// accumulated rewards, see [`BondExtra`]. /// /// Bonding extra funds implies an automatic payout of all pending rewards as well. // NOTE: this transaction is implemented with the sole purpose of readability and // correctness, not optimization. We read/write several storage items multiple times instead - // of just once, in the spirit reusing code. + // of just once, in the spirit of reusing code. #[pallet::weight( T::WeightInfo::bond_extra_transfer() .max(T::WeightInfo::bond_extra_reward()) @@ -1551,18 +1604,17 @@ pub mod pallet { // payout related stuff: we must claim the payouts, and updated recorded payout data // before updating the bonded pool points, similar to that of `join` transaction. reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; - // TODO: optimize this to not touch the free balance of `who ` at all in benchmarks. - // Currently, bonding rewards is like a batch. In the same PR, also make this function - // take a boolean argument that make it either 100% pure (no storage update), or make it - // also emit event and do the transfer. #11671 - let claimed = + + // we have to claim the pending_rewards every time we change the bonded amount. + if matches!(extra, BondExtra::FreeBalance(_)) { Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + } let (points_issued, bonded) = match extra { BondExtra::FreeBalance(amount) => - (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + (bonded_pool.try_bond_funds(&who, amount, BondType::Later, true)?, amount), BondExtra::Rewards => - (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed), + bonded_pool.try_bond_funds_from_rewards(&who, &mut member, &mut reward_pool)?, }; bonded_pool.ok_to_be_open(bonded)?; @@ -1580,7 +1632,7 @@ pub mod pallet { } /// A bonded member can use this to claim their payout based on the rewards that the pool - /// has accumulated since their last claimed payout (OR since joining if this is there first + /// has accumulated since their last claimed payout (OR since joining if this is their first /// time claiming rewards). The payout will be transferred to the member's account. /// /// The member will earn rewards pro rata based on the members stake vs the sum of the @@ -1892,7 +1944,7 @@ pub mod pallet { ); bonded_pool.try_inc_members()?; - let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; + let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create, true)?; T::Currency::transfer( &who, @@ -2301,24 +2353,64 @@ impl Pallet { .div(current_points) } - /// If the member has some rewards, transfer a payout from the reward pool to the member. - // Emits events and potentially modifies pool state if any arithmetic saturates, but does - // not persist any of the mutable inputs to storage. - fn do_reward_payout( - member_account: &T::AccountId, + /// Calculates the rewards for the given member, and makes a payout to the + /// defined `payout_recipient` account. + /// + /// Returns the amount of pending_rewards that the member has accumulated, + /// that are now transfered to the `payout_recipient`. + fn calculate_and_payout_rewards( member: &mut PoolMember, bonded_pool: &mut BondedPool, reward_pool: &mut RewardPool, + payout_recipient: PayoutRecipient, ) -> Result, DispatchError> { - debug_assert_eq!(member.pool_id, bonded_pool.id); - - // a member who has no skin in the game anymore cannot claim any rewards. - ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); + let (pending_rewards, current_reward_counter) = + Self::calculate_rewards(member, bonded_pool, reward_pool)?; + Self::payout_rewards( + member, + bonded_pool, + reward_pool, + pending_rewards, + current_reward_counter, + payout_recipient, + )?; + Ok(pending_rewards) + } + /// Only calculates the rewards for the given member without changing any + /// data. + /// + /// Returns the pending rewards of the member and the + /// `current_reward_counter` of the reward pool + fn calculate_rewards( + member: &PoolMember, + bonded_pool: &BondedPool, + reward_pool: &RewardPool, + ) -> Result<(BalanceOf, T::RewardCounter), DispatchError> { let current_reward_counter = reward_pool.current_reward_counter(bonded_pool.id, bonded_pool.points)?; let pending_rewards = member.pending_rewards(current_reward_counter)?; + Ok((pending_rewards, current_reward_counter)) + } + + /// Does the actual payout of the accumulated rewards by the member. + /// + /// Returns the amount of pending rewards that got paid out. + /// Emits the `PaidOut` event. + fn payout_rewards( + member: &mut PoolMember, + bonded_pool: &mut BondedPool, + reward_pool: &mut RewardPool, + pending_rewards: BalanceOf, + current_reward_counter: T::RewardCounter, + payout_recipient: PayoutRecipient, + ) -> Result, DispatchError> { + debug_assert_eq!(member.pool_id, bonded_pool.id); + + // a member who has no skin in the game anymore cannot claim any rewards. + ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); + if pending_rewards.is_zero() { return Ok(pending_rewards) } @@ -2327,16 +2419,22 @@ impl Pallet { member.last_recorded_reward_counter = current_reward_counter; reward_pool.register_claimed_reward(pending_rewards); - // Transfer payout to the member. + let (member_acc, recipient) = match payout_recipient { + PayoutRecipient::::MemberAccount(member_acc) => (member_acc.clone(), member_acc), + PayoutRecipient::::BondedAccount(member_acc, bonded_acc) => (member_acc, bonded_acc), + }; + + // Transfer payout to the recipient. T::Currency::transfer( &bonded_pool.reward_account(), - &member_account, + &recipient, pending_rewards, ExistenceRequirement::AllowDeath, )?; Self::deposit_event(Event::::PaidOut { - member: member_account.clone(), + member: member_acc, + recipient: recipient.clone(), pool_id: member.pool_id, payout: pending_rewards, }); @@ -2344,6 +2442,23 @@ impl Pallet { Ok(pending_rewards) } + /// If the member has some rewards, transfer a payout from the reward pool to the member. + // Emits events and potentially modifies pool state if any arithmetic saturates, but does + // not persist any of the mutable inputs to storage. + fn do_reward_payout( + member_account: &T::AccountId, + member: &mut PoolMember, + bonded_pool: &mut BondedPool, + reward_pool: &mut RewardPool, + ) -> Result, DispatchError> { + Self::calculate_and_payout_rewards( + member, + bonded_pool, + reward_pool, + PayoutRecipient::::MemberAccount(member_account.clone()), + ) + } + /// Ensure the correctness of the state of this pallet. /// /// This should be valid before or after each state transition of this pallet. diff --git a/frame/nomination-pools/src/migration.rs b/frame/nomination-pools/src/migration.rs index f2abfe29dfbf7..bde99039f6be6 100644 --- a/frame/nomination-pools/src/migration.rs +++ b/frame/nomination-pools/src/migration.rs @@ -279,6 +279,7 @@ pub mod v2 { } Pallet::::deposit_event(Event::::PaidOut { + recipient: who.clone(), member: who.clone(), pool_id: id, payout: last_claim, diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 5138c55afccac..94f69eb04cf5e 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -21,6 +21,13 @@ pub fn default_reward_account() -> AccountId { Pools::create_reward_account(1) } +pub fn default_pool_reward_counter() -> FixedU128 { + RewardPools::::get(1) + .unwrap() + .current_reward_counter(1, BondedPools::::get(1).unwrap().points) + .unwrap() +} + parameter_types! { pub static MinJoinBondConfig: Balance = 2; pub static CurrentEra: EraIndex = 0; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 5aa8e97266e0d..6510dca7b0d30 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -696,7 +696,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 },] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 },] ); // last recorded reward counter at the time of this member's payout is 1 assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 1)); @@ -712,7 +712,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] + vec![Event::PaidOut { recipient: 40, member: 40, pool_id: 1, payout: 40 }] ); assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 1)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 50)); @@ -725,7 +725,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + vec![Event::PaidOut { recipient: 50, member: 50, pool_id: 1, payout: 50 }] ); assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 1)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); @@ -741,7 +741,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 5 }] ); assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 1.5)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 105)); @@ -754,7 +754,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + vec![Event::PaidOut { recipient: 40, member: 40, pool_id: 1, payout: 20 }] ); assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 1.5)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 125)); @@ -771,7 +771,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + vec![Event::PaidOut { recipient: 50, member: 50, pool_id: 1, payout: 50 }] ); assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 2.0)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 175)); @@ -784,7 +784,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 5 }] ); assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 2)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 180)); @@ -801,7 +801,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 40 }] ); // We expect a payout of 40 @@ -820,7 +820,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 2 }] ); assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 6.2)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 222)); @@ -833,7 +833,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] + vec![Event::PaidOut { recipient: 40, member: 40, pool_id: 1, payout: 188 }] ); assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 6.2)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 410)); @@ -846,7 +846,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] + vec![Event::PaidOut { recipient: 50, member: 50, pool_id: 1, payout: 210 }] ); assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 6.2)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 620)); @@ -907,7 +907,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 5 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 5 } ] ); assert_eq!(payout, 5); @@ -925,7 +925,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }] ); assert_eq!(payout, 10); assert_eq!(reward_pool, rew(0, 0, 15)); @@ -984,7 +984,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }] ); assert_eq!(payout, 10); assert_eq!(del_10, del(10, 1)); @@ -998,7 +998,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] + vec![Event::PaidOut { recipient: 40, member: 40, pool_id: 1, payout: 40 }] ); assert_eq!(payout, 40); assert_eq!(del_40, del(40, 1)); @@ -1012,7 +1012,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + vec![Event::PaidOut { recipient: 50, member: 50, pool_id: 1, payout: 50 }] ); assert_eq!(payout, 50); assert_eq!(del_50, del(50, 1)); @@ -1029,7 +1029,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 5 }] ); assert_eq!(payout, 5); assert_eq!(del_10, del_float(10, 1.5)); @@ -1043,7 +1043,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + vec![Event::PaidOut { recipient: 40, member: 40, pool_id: 1, payout: 20 }] ); assert_eq!(payout, 20); assert_eq!(del_40, del_float(40, 1.5)); @@ -1060,7 +1060,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + vec![Event::PaidOut { recipient: 50, member: 50, pool_id: 1, payout: 50 }] ); assert_eq!(payout, 50); assert_eq!(del_50, del_float(50, 2.0)); @@ -1074,7 +1074,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 5 }] ); assert_eq!(payout, 5); assert_eq!(del_10, del_float(10, 2.0)); @@ -1091,7 +1091,7 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 40 }] ); assert_eq!(payout, 40); assert_eq!(del_10, del_float(10, 6.0)); @@ -1154,8 +1154,8 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 10 }, ] ); @@ -1168,8 +1168,8 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 10 }, ] ); }); @@ -1197,8 +1197,8 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 3 + 3 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 3 + 3 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 3 }, ] ); @@ -1211,8 +1211,8 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 4 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 4 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 4 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 4 }, ] ); @@ -1225,8 +1225,8 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 3 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 3 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 3 }, ] ); }); @@ -1261,9 +1261,19 @@ mod claim_payout { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 30 + 100 / 2 + 60 / 3 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 100 / 2 + 60 / 3 }, - Event::PaidOut { member: 30, pool_id: 1, payout: 60 / 3 }, + Event::PaidOut { + recipient: 10, + member: 10, + pool_id: 1, + payout: 30 + 100 / 2 + 60 / 3 + }, + Event::PaidOut { + recipient: 20, + member: 20, + pool_id: 1, + payout: 100 / 2 + 60 / 3 + }, + Event::PaidOut { recipient: 30, member: 30, pool_id: 1, payout: 60 / 3 }, ] ); @@ -1277,9 +1287,9 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, - Event::PaidOut { member: 30, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 30, member: 30, pool_id: 1, payout: 10 }, ] ); }); @@ -1309,9 +1319,9 @@ mod claim_payout { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 20 }, - Event::PaidOut { member: 30, pool_id: 1, payout: 10 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 20 }, + Event::PaidOut { recipient: 30, member: 30, pool_id: 1, payout: 10 } ] ); @@ -1329,9 +1339,9 @@ mod claim_payout { pool_events_since_last_call(), vec![ Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: false }, - Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 40 }, - Event::PaidOut { member: 30, pool_id: 1, payout: 40 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 40 }, + Event::PaidOut { recipient: 30, member: 30, pool_id: 1, payout: 40 } ] ); }); @@ -1357,8 +1367,8 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 20 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 20 } ] ); @@ -1375,8 +1385,8 @@ mod claim_payout { pool_events_since_last_call(), vec![ Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, - Event::PaidOut { member: 10, pool_id: 1, payout: 50 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 50 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 50 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 50 }, ] ); }); @@ -1406,8 +1416,8 @@ mod claim_payout { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 20 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 20 } ] ); @@ -1421,8 +1431,8 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 40 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 40 } ] ); @@ -1436,8 +1446,8 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 40 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 40 } ] ); @@ -1446,7 +1456,12 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 30, pool_id: 1, payout: 10 + 20 + 20 }] + vec![Event::PaidOut { + recipient: 30, + member: 30, + pool_id: 1, + payout: 10 + 20 + 20 + }] ); }); } @@ -1471,7 +1486,7 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 10 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 } ] ); @@ -1481,7 +1496,7 @@ mod claim_payout { // and 20 bonds more -- they should not have more share of this reward. assert_ok!(Pools::bond_extra(Origin::signed(20), BondExtra::FreeBalance(10))); - // everyone claim. + // everyone claims. assert_ok!(Pools::claim_payout(Origin::signed(10))); assert_ok!(Pools::claim_payout(Origin::signed(20))); @@ -1489,9 +1504,9 @@ mod claim_payout { pool_events_since_last_call(), vec![ // 20 + 40, which means the extra amount they bonded did not impact us. - Event::PaidOut { member: 20, pool_id: 1, payout: 60 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 60 }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false }, - Event::PaidOut { member: 10, pool_id: 1, payout: 20 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 20 } ] ); @@ -1505,8 +1520,8 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 45 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 15 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 45 } ] ); }); @@ -1573,7 +1588,7 @@ mod claim_payout { Event::Bonded { member: 20, pool_id: 2, bonded: 10, joined: true }, Event::Created { depositor: 30, pool_id: 3 }, Event::Bonded { member: 30, pool_id: 3, bonded: 10, joined: true }, - Event::PaidOut { member: 30, pool_id: 3, payout: 10 } + Event::PaidOut { recipient: 30, member: 30, pool_id: 3, payout: 10 } ] ); }) @@ -1711,9 +1726,9 @@ mod claim_payout { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, - Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 15 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, - Event::PaidOut { member: 20, pool_id: 1, payout: 15 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 15 }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false } ] ); @@ -1809,12 +1824,12 @@ mod claim_payout { Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true }, Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, - Event::PaidOut { member: 30, pool_id: 1, payout: 15 }, + Event::PaidOut { recipient: 30, member: 30, pool_id: 1, payout: 15 }, Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10, era: 3 }, Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5, era: 3 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 7 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 7 }, Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 3 }, - Event::PaidOut { member: 10, pool_id: 1, payout: 7 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 7 } ] ); }) @@ -1845,8 +1860,8 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 13 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 26 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 13 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 26 } ] ); @@ -1930,10 +1945,10 @@ mod claim_payout { bonded: 5000000000000000, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 100000000 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 150000000 }, - Event::PaidOut { member: 21, pool_id: 1, payout: 250000000 }, - Event::PaidOut { member: 22, pool_id: 1, payout: 500000000 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 100000000 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 150000000 }, + Event::PaidOut { recipient: 21, member: 21, pool_id: 1, payout: 250000000 }, + Event::PaidOut { recipient: 22, member: 22, pool_id: 1, payout: 500000000 } ] ); }) @@ -2268,7 +2283,7 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::PaidOut { member: 40, pool_id: 1, payout: 40 }, + Event::PaidOut { recipient: 40, member: 40, pool_id: 1, payout: 40 }, Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6, era: 3 } ] ); @@ -2310,7 +2325,7 @@ mod unbond { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 550, pool_id: 1, payout: 550 }, + Event::PaidOut { recipient: 550, member: 550, pool_id: 1, payout: 550 }, Event::Unbonded { member: 550, pool_id: 1, @@ -2354,7 +2369,7 @@ mod unbond { Event::MemberRemoved { pool_id: 1, member: 40 }, Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 }, Event::MemberRemoved { pool_id: 1, member: 550 }, - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10 }, Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2, era: 6 } ] ); @@ -2890,7 +2905,7 @@ mod unbond { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 10 }, Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2, era: 3 } ] ); @@ -2906,7 +2921,7 @@ mod unbond { pool_events_since_last_call(), vec![ // 2/3 of ed, which is 20's share. - Event::PaidOut { member: 20, pool_id: 1, payout: 6 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 6 }, Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 } ] ); @@ -2921,7 +2936,7 @@ mod unbond { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 3 }, Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 } ] ); @@ -4093,6 +4108,7 @@ mod set_configs { mod bond_extra { use super::*; use crate::Event; + use sp_runtime::FixedU128; #[test] fn bond_extra_from_free_balance_creator() { @@ -4164,7 +4180,12 @@ mod bond_extra { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: claimable_reward }, + Event::PaidOut { + recipient: default_bonded_account(), + member: 10, + pool_id: 1, + payout: claimable_reward + }, Event::Bonded { member: 10, pool_id: 1, @@ -4184,7 +4205,7 @@ mod bond_extra { Balances::make_free_balance_be(&default_reward_account(), 8); // ... if which only 3 is claimable to make sure the reward account does not die. let claimable_reward = 8 - ExistentialDeposit::get(); - // NOTE: easier to read of we use 3, so let's use the number instead of variable. + // NOTE: easier to read if we use 3, so let's use the number instead of variable. assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); // given @@ -4219,14 +4240,127 @@ mod bond_extra { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 1 }, + Event::PaidOut { + recipient: default_bonded_account(), + member: 10, + pool_id: 1, + payout: 1 + }, Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false }, - Event::PaidOut { member: 20, pool_id: 1, payout: 2 }, + Event::PaidOut { + recipient: default_bonded_account(), + member: 20, + pool_id: 1, + payout: 2 + }, Event::Bonded { member: 20, pool_id: 1, bonded: 2, joined: false } ] ); }) } + + #[test] + fn calculate_rewards_works() { + ExtBuilder::default().build_and_execute(|| { + // give some balance. + Balances::make_free_balance_be(&10, 100); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 100); + + let (member, bonded_pool, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + + // then + assert_eq!( + Pools::calculate_rewards(&member, &bonded_pool, &reward_pool).unwrap(), + (0, FixedU128::from(0)) + ); + + // given some free balance. + Balances::make_free_balance_be(&default_reward_account(), 7); + + let claimable_reward = 7 - ExistentialDeposit::get(); + let current_reward_counter = default_pool_reward_counter(); + + // this should hold true. + assert_eq!( + Pools::calculate_rewards(&member, &bonded_pool, &reward_pool).unwrap(), + (claimable_reward, current_reward_counter) + ); + }) + } + + #[test] + fn try_bond_funds_from_rewards_works() { + ExtBuilder::default().build_and_execute(|| { + // given some free balance. + Balances::make_free_balance_be(&default_reward_account(), 7); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 35); + + let (mut member, mut bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); + + // when + assert_ok!(bonded_pool.try_bond_funds_from_rewards(&10, &mut member, &mut reward_pool)); + + // then + assert_eq!(Balances::free_balance(10), 35); + // The points are still not updated. They are updated in the later + // part of the `bond_extra` function, which is not called here. + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + }) + } + + #[test] + fn payout_rewards_to_bonded_account_works() { + ExtBuilder::default().build_and_execute(|| { + // given some free balance. + Balances::make_free_balance_be(&default_reward_account(), 7); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 35); + + let (mut member, mut bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); + + let claimable_reward = 7 - ExistentialDeposit::get(); + let current_reward_counter = default_pool_reward_counter(); + + // when + assert_ok!(Pools::payout_rewards( + &mut member, + &mut bonded_pool, + &mut reward_pool, + claimable_reward, + current_reward_counter, + PayoutRecipient::BondedAccount(10, default_bonded_account()) + )); + + // then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { + recipient: default_bonded_account(), + member: 10, + pool_id: 1, + payout: 2 + } + ] + ); + }) + } } mod update_roles { @@ -4401,8 +4535,6 @@ mod update_roles { } mod reward_counter_precision { - use sp_runtime::FixedU128; - use super::*; const DOT: Balance = 10u128.pow(10u32); @@ -4418,13 +4550,6 @@ mod reward_counter_precision { start } - fn default_pool_reward_counter() -> FixedU128 { - RewardPools::::get(1) - .unwrap() - .current_reward_counter(1, BondedPools::::get(1).unwrap().points) - .unwrap() - } - fn pending_rewards(of: AccountId) -> Option> { let member = PoolMembers::::get(of).unwrap(); assert_eq!(member.pool_id, 1); @@ -4469,7 +4594,7 @@ mod reward_counter_precision { assert_ok!(Pools::claim_payout(Origin::signed(10))); assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 1173 }] + vec![Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 1173 }] ); }) } @@ -4519,7 +4644,12 @@ mod reward_counter_precision { assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 15937424600999999996 }] + vec![Event::PaidOut { + recipient: 10, + member: 10, + pool_id: 1, + payout: 15937424600999999996 + }] ); // now let a small member join with 10 DOTs. @@ -4535,7 +4665,7 @@ mod reward_counter_precision { vec![ Event::Bonded { member: 30, pool_id: 1, bonded: 100000000000, joined: true }, // quite small, but working fine. - Event::PaidOut { member: 30, pool_id: 1, payout: 38 } + Event::PaidOut { recipient: 30, member: 30, pool_id: 1, payout: 38 } ] ); }) @@ -4612,7 +4742,7 @@ mod reward_counter_precision { bonded: 100000000000, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 9999997 } ] ); @@ -4625,7 +4755,12 @@ mod reward_counter_precision { assert_ok!(Pools::claim_payout(Origin::signed(10))); assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }] + vec![Event::PaidOut { + recipient: 10, + member: 10, + pool_id: 1, + payout: 10000000 + }] ); // earn some more rewards, this time 20 can also claim. @@ -4638,8 +4773,8 @@ mod reward_counter_precision { assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }, - Event::PaidOut { member: 20, pool_id: 1, payout: 1 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 10000000 }, + Event::PaidOut { recipient: 20, member: 20, pool_id: 1, payout: 1 } ] ); }); @@ -4688,7 +4823,7 @@ mod reward_counter_precision { bonded: 100000000000, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 } + Event::PaidOut { recipient: 10, member: 10, pool_id: 1, payout: 9999997 } ] );