difftreelog
fix the behavior of the `appPromotion::payoutStakers` extrinsic, in which one staker could be skipped when called sequentially.
in: master
3 files changed
pallets/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(¤t_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()?;
}
tests/src/app-promotion.test.tsdiffbeforeafterboth33 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();383839 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.configuration41 .setAppPromotionConfigurationOverride({42 recalculationInterval: LOCKING_PERIOD,43 pendingInterval: UNLOCKING_PERIOD})], true);42 });44 });43 });45 });444645 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 90056 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}) => {tests/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.