git.delta.rocks / unique-network / refs/commits / 2a3de5473dad

difftreelog

fix the behavior of the `appPromotion::payoutStakers` extrinsic, in which one staker could be skipped when called sequentially.

PraetorP2022-12-14parent: #bf956bf.patch.diff
in: master

3 files changed

modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -255,11 +255,11 @@
 		ValueQuery,
 	>;
 
-	/// Stores a key for record for which the next revenue recalculation would be performed.
+	/// Stores a key for record for which the revenue recalculation was performed.
 	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
 	#[pallet::storage]
 	#[pallet::getter(fn get_next_calculated_record)]
-	pub type NextCalculatedRecord<T: Config> =
+	pub type PreviousCalculatedRecord<T: Config> =
 		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;
 
 	#[pallet::hooks]
@@ -577,7 +577,7 @@
 			let mut stakers_number = stakers_number.unwrap_or(DEFAULT_NUMBER_PAYOUTS);
 
 			ensure!(
-				stakers_number <= config.max_stakers_per_calculation,
+				stakers_number <= config.max_stakers_per_calculation && stakers_number != 0,
 				Error::<T>::NoPermission
 			);
 
@@ -595,7 +595,7 @@
 			let mut storage_iterator = Self::get_next_calculated_key()
 				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));
 
-			NextCalculatedRecord::<T>::set(None);
+			PreviousCalculatedRecord::<T>::set(None);
 
 			{
 				let last_id = RefCell::new(None);
@@ -640,10 +640,6 @@
 					(amount, next_recalc_block_for_stake),
 				)) = storage_iterator.next()
 				{
-					if stakers_number == 0 {
-						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));
-						break;
-					}
 					if last_id.borrow().as_ref() != Some(&current_id) {
 						flush_stake()?;
 						*last_id.borrow_mut() = Some(current_id.clone());
@@ -662,6 +658,13 @@
 							&mut *income_acc.borrow_mut(),
 						);
 					}
+
+					if stakers_number == 0 {
+						if storage_iterator.next().is_some() {
+							PreviousCalculatedRecord::<T>::set(Some((current_id, staked_block)));
+						}
+						break;
+					}
 				}
 				flush_stake()?;
 			}
modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
33 requirePalletsOrSkip(this, helper, [Pallets.AppPromotion]);33 requirePalletsOrSkip(this, helper, [Pallets.AppPromotion]);
34 donor = await privateKey({filename: __filename});34 donor = await privateKey({filename: __filename});
35 palletAddress = helper.arrange.calculatePalletAddress('appstake');35 palletAddress = helper.arrange.calculatePalletAddress('appstake');
36 palletAdmin = await privateKey('//PromotionAdmin');36 palletAdmin = await privateKey('//Alice');
37 nominal = helper.balance.getOneTokenNominal();37 nominal = helper.balance.getOneTokenNominal();
3838
39 const accountBalances = new Array(100);
40 accountBalances.fill(1000n);39 await helper.executeExtrinsic(palletAdmin, 'api.tx.sudo.sudo', [helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})]);
41 accounts = await helper.arrange.createAccounts(accountBalances, donor); // create accounts-pool to speed up tests40 await helper.executeExtrinsic(palletAdmin, 'api.tx.sudo.sudo', [helper.api!.tx.configuration
41 .setAppPromotionConfigurationOverride({
42 recalculationInterval: LOCKING_PERIOD,
43 pendingInterval: UNLOCKING_PERIOD})], true);
42 });44 });
43 });45 });
4446
45 describe('stake extrinsic', () => { 47 describe('stake extrinsic', () => {
46 itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {48 itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {
47 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];49
48 const totalStakedBefore = await helper.staking.getTotalStaked();50
49
50 // Minimum stake amount is 100:
51 await expect(helper.staking.stake(staker, 100n * nominal - 1n)).to.be.rejected;
52 await helper.staking.stake(staker, 100n * nominal);
53
54 // Staker balance is: miscFrozen: 100, feeFrozen: 100, reserved: 0n...
55 // ...so he can not transfer 900
56 expect (await helper.balance.getSubstrateFull(staker.address)).to.contain({miscFrozen: 100n * nominal, feeFrozen: 100n * nominal, reserved: 0n});
57 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 900n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');
58
59 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(100n * nominal);
60 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);
61 // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo?
62 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased
63
64
65 await helper.staking.stake(staker, 200n * nominal);
66 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal);
67 const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
68 expect(totalStakedPerBlock[0].amount).to.equal(100n * nominal);
69 expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);
70 });51 });
71 52
72 itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {53 itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -23,11 +23,6 @@
        **/
       admin: AugmentedQuery<ApiType, () => Observable<Option<AccountId32>>, []> & QueryableStorageEntry<ApiType, []>;
       /**
-       * Stores a key for record for which the next revenue recalculation would be performed.
-       * If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
-       **/
-      nextCalculatedRecord: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[AccountId32, u32]>>>, []> & QueryableStorageEntry<ApiType, []>;
-      /**
        * Stores amount of stakes for an `Account`.
        * 
        * * **Key** - Staker account.
@@ -35,6 +30,11 @@
        **/
       pendingUnstake: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Vec<ITuple<[AccountId32, u128]>>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
+       * Stores a key for record for which the next revenue recalculation would be performed.
+       * If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
+       **/
+      previousCalculatedRecord: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[AccountId32, u32]>>>, []> & QueryableStorageEntry<ApiType, []>;
+      /**
        * Stores the amount of tokens staked by account in the blocknumber.
        * 
        * * **Key1** - Staker account.