--- a/Cargo.lock +++ b/Cargo.lock @@ -5524,13 +5524,14 @@ [[package]] name = "pallet-app-promotion" -version = "0.1.0" +version = "0.1.1" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", "pallet-common", + "pallet-configuration", "pallet-evm", "pallet-evm-contract-helpers", "pallet-evm-migration", @@ -5784,7 +5785,7 @@ [[package]] name = "pallet-configuration" -version = "0.1.1" +version = "0.1.2" dependencies = [ "fp-evm", "frame-support", --- /dev/null +++ b/pallets/app-promotion/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log + +All notable changes to this project will be documented in this file. + + + +## [0.1.1] - 2022-12-13 + +### Added + +- The ability to configure pallet `app-promotion` via the `configuration` palette --- a/pallets/app-promotion/Cargo.toml +++ b/pallets/app-promotion/Cargo.toml @@ -9,7 +9,7 @@ license = 'GPLv3' name = 'pallet-app-promotion' repository = 'https://github.com/UniqueNetwork/unique-chain' -version = '0.1.0' +version = '0.1.1' [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] @@ -68,6 +68,7 @@ up-data-structs = { default-features = false, path = "../../primitives/data-structs" } pallet-common = { default-features = false, path = "../common" } +pallet-configuration = { default-features = false, path = "../configuration" } pallet-unique = { default-features = false, path = "../unique" } pallet-evm-contract-helpers = { default-features = false, path = "../evm-contract-helpers" } pallet-evm-migration = { default-features = false, path = "../evm-migration" } --- a/pallets/app-promotion/src/lib.rs +++ b/pallets/app-promotion/src/lib.rs @@ -102,7 +102,9 @@ use frame_system::pallet_prelude::*; #[pallet::config] - pub trait Config: frame_system::Config + pallet_evm::Config { + pub trait Config: + frame_system::Config + pallet_evm::Config + pallet_configuration::Config + { /// Type to interact with the native token type Currency: ExtendedLockableCurrency + ReservableCurrency; @@ -330,6 +332,7 @@ amount >= >::from(100u128) * T::Nominal::get(), ArithmeticError::Underflow ); + let config = >::get(); let balance = <::Currency as Currency>::free_balance(&staker_id); @@ -351,7 +354,7 @@ // Calculation of the number of recalculation periods, // after how much the first interest calculation should be performed for the stake let recalculate_after_interval: T::BlockNumber = - if block_number % T::RecalculationInterval::get() == 0u32.into() { + if block_number % config.recalculation_interval == 0u32.into() { 1u32.into() } else { 2u32.into() @@ -359,9 +362,9 @@ // Сalculation of the number of the relay block // in which it is necessary to accrue remuneration for the stake. - let recalc_block = (block_number / T::RecalculationInterval::get() + let recalc_block = (block_number / config.recalculation_interval + recalculate_after_interval) - * T::RecalculationInterval::get(); + * config.recalculation_interval; >::insert((&staker_id, block_number), { let mut balance_and_recalc_block = >::get((&staker_id, block_number)); @@ -393,9 +396,10 @@ #[pallet::weight(::WeightInfo::unstake())] pub fn unstake(staker: OriginFor) -> DispatchResultWithPostInfo { let staker_id = ensure_signed(staker)?; + let config = >::get(); // calculate block number where the sum would be free - let block = >::block_number() + T::PendingInterval::get(); + let block = >::block_number() + config.pending_interval; let mut pendings = >::get(block); @@ -568,15 +572,25 @@ admin_id == Admin::::get().ok_or(Error::::AdminNotSet)?, Error::::NoPermission ); + let config = >::get(); + let mut stakers_number = stakers_number.unwrap_or(DEFAULT_NUMBER_PAYOUTS); + + ensure!( + stakers_number <= config.max_stakers_per_calculation, + Error::::NoPermission + ); + // calculate the number of the current recalculation block, // this is necessary in order to understand which stakers we should calculate interest - let current_recalc_block = - Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number()); + let current_recalc_block = Self::get_current_recalc_block( + T::RelayBlockNumberProvider::current_block_number(), + &config, + ); // calculate the number of the next recalculation block, // this value is set for the stakers to whom the recalculation will be performed - let next_recalc_block = current_recalc_block + T::RecalculationInterval::get(); + let next_recalc_block = current_recalc_block + config.recalculation_interval; let mut storage_iterator = Self::get_next_calculated_key() .map_or(Staked::::iter(), |key| Staked::::iter_from(key)); @@ -584,7 +598,6 @@ NextCalculatedRecord::::set(None); { - let mut stakers_number = stakers_number.unwrap_or(20); let last_id = RefCell::new(None); let income_acc = RefCell::new(BalanceOf::::default()); let amount_acc = RefCell::new(BalanceOf::::default()); @@ -644,8 +657,8 @@ next_recalc_block, amount, ((current_recalc_block - next_recalc_block_for_stake) - / T::RecalculationInterval::get()) - .into() + 1, + / config.recalculation_interval) + .into() + 1, &mut *income_acc.borrow_mut(), ); } @@ -810,15 +823,19 @@ where I: EncodeLike> + Balance, { + let config = >::get(); let mut income = base; - (0..iters).for_each(|_| income += T::IntervalIncome::get() * income); + (0..iters).for_each(|_| income += config.interval_income * income); income - base } - fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber { - (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get() + fn get_current_recalc_block( + current_relay_block: T::BlockNumber, + config: &PalletConfiguration, + ) -> T::BlockNumber { + (current_relay_block / config.recalculation_interval) * config.recalculation_interval } fn get_next_calculated_key() -> Option> { --- a/pallets/app-promotion/src/types.rs +++ b/pallets/app-promotion/src/types.rs @@ -4,10 +4,15 @@ use pallet_balances::{BalanceLock, Config as BalancesConfig, Pallet as PalletBalances}; use pallet_common::CollectionHandle; -use sp_runtime::DispatchError; +use sp_runtime::{DispatchError, Perbill}; use up_data_structs::{CollectionId}; use sp_std::borrow::ToOwned; use pallet_evm_contract_helpers::{Pallet as EvmHelpersPallet, Config as EvmHelpersConfig}; +use pallet_configuration::{AppPromomotionConfigurationOverride}; +use sp_core::Get; + +const MAX_NUMBER_PAYOUTS: u8 = 100; +pub(crate) const DEFAULT_NUMBER_PAYOUTS: u8 = 20; /// This trait was defined because `LockableCurrency` /// has no way to know the state of the lock for an account. @@ -128,3 +133,24 @@ Ok(Self::get_sponsor(contract_address)) } } +pub(crate) struct PalletConfiguration { + /// In relay blocks. + pub recalculation_interval: T::BlockNumber, + /// In parachain blocks. + pub pending_interval: T::BlockNumber, + /// Value for `RecalculationInterval` based on 0.05% per 24h. + pub interval_income: Perbill, + /// Maximum allowable number of stakers calculated per call of the `app-promotion::PayoutStakers` extrinsic. + pub max_stakers_per_calculation: u8, +} +impl PalletConfiguration { + pub fn get() -> Self { + let config = >::get().unwrap_or_default(); + Self { + recalculation_interval: config.0.unwrap_or(T::RecalculationInterval::get()), + pending_interval: config.2.unwrap_or(T::PendingInterval::get()), + interval_income: config.1.unwrap_or(T::IntervalIncome::get()), + max_stakers_per_calculation: config.3.unwrap_or(MAX_NUMBER_PAYOUTS), + } + } +} --- a/pallets/configuration/CHANGELOG.md +++ b/pallets/configuration/CHANGELOG.md @@ -1,4 +1,10 @@ +## [0.1.2] - 2022-12-13 + +### Added + +- The ability to configure pallet `app-promotion` via the `configuration` palette + ## [v0.1.1] 2022-08-16 ### Other changes --- a/pallets/configuration/Cargo.toml +++ b/pallets/configuration/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-configuration" -version = "0.1.1" +version = "0.1.2" edition = "2021" [dependencies] --- a/pallets/configuration/src/lib.rs +++ b/pallets/configuration/src/lib.rs @@ -51,6 +51,10 @@ #[pallet::constant] type MaxOverridedAllowedLocations: Get; + #[pallet::constant] + type AppPromotionDailyRate: Get; + #[pallet::constant] + type DayRelayBlocks: Get; } #[pallet::storage] @@ -70,6 +74,17 @@ QueryKind = OptionQuery, >; + #[pallet::storage] + pub type AppPromomotionConfigurationOverride = StorageValue< + Value = ( + Option, + Option, + Option, + Option, + ), + QueryKind = OptionQuery, + >; + #[pallet::call] impl Pallet { #[pallet::weight(T::DbWeight::get().writes(1))] @@ -109,6 +124,39 @@ >::set(locations); Ok(()) } + + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_app_promotion_configuration_override( + origin: OriginFor, + recalculation_interval: Option, + pending_interval: Option, + stakers_payout_limit: Option, + ) -> DispatchResult { + let _sender = ensure_root(origin)?; + + if recalculation_interval.is_none() + && pending_interval.is_none() + && stakers_payout_limit.is_none() + { + >::kill(); + } else { + let mut current_config = + >::take().unwrap_or_default(); + + recalculation_interval.map(|b| { + current_config.0 = Some(b); + current_config.1 = Some( + Perbill::from_rational(b, T::DayRelayBlocks::get()) + * T::AppPromotionDailyRate::get(), + ) + }); + pending_interval.map(|b| current_config.2 = Some(b)); + stakers_payout_limit.map(|p| current_config.3 = Some(p)); + >::set(Some(current_config)); + } + + Ok(()) + } } #[pallet::pallet] --- a/runtime/common/config/pallets/app_promotion.rs +++ b/runtime/common/config/pallets/app_promotion.rs @@ -26,16 +26,6 @@ types::Balance, }; -#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))] -parameter_types! { - pub const AppPromotionId: PalletId = PalletId(*b"appstake"); - pub const RecalculationInterval: BlockNumber = 8; - pub const PendingInterval: BlockNumber = 4; - pub const Nominal: Balance = UNIQUE; - pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000); -} - -#[cfg(any(feature = "unique-runtime", feature = "quartz-runtime"))] parameter_types! { pub const AppPromotionId: PalletId = PalletId(*b"appstake"); pub const RecalculationInterval: BlockNumber = RELAY_DAYS; --- a/runtime/common/config/pallets/mod.rs +++ b/runtime/common/config/pallets/mod.rs @@ -33,6 +33,7 @@ use up_data_structs::{ mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}, }; +use sp_arithmetic::Perbill; #[cfg(feature = "rmrk")] pub mod rmrk; @@ -98,10 +99,16 @@ type RefungibleExtensionsWeightInfo = CommonWeights; } +parameter_types! { + pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000); + pub const DayRelayBlocks: BlockNumber = RELAY_DAYS; +} impl pallet_configuration::Config for Runtime { type DefaultWeightToFeeCoefficient = ConstU32<{ up_common::constants::WEIGHT_TO_FEE_COEFF }>; type DefaultMinGasPrice = ConstU64<{ up_common::constants::MIN_GAS_PRICE }>; type MaxOverridedAllowedLocations = ConstU32<16>; + type AppPromotionDailyRate = AppPromotionDailyRate; + type DayRelayBlocks = DayRelayBlocks; } impl pallet_maintenance::Config for Runtime { --- a/tests/src/app-promotion.test.ts +++ b/tests/src/app-promotion.test.ts @@ -31,11 +31,14 @@ before(async function () { await usingPlaygrounds(async (helper, privateKey) => { requirePalletsOrSkip(this, helper, [Pallets.AppPromotion]); + const alice = await privateKey('//Alice'); donor = await privateKey({filename: __filename}); palletAddress = helper.arrange.calculatePalletAddress('appstake'); palletAdmin = await privateKey('//PromotionAdmin'); nominal = helper.balance.getOneTokenNominal(); accounts = await helper.arrange.createCrowd(100, 1000n, donor); // create accounts-pool to speed up tests + const api = helper.getApi(); + await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setAppPromotionConfigurationOverride(LOCKING_PERIOD, UNLOCKING_PERIOD, null))); }); }); --- a/tests/src/interfaces/augment-api-consts.ts +++ b/tests/src/interfaces/augment-api-consts.ts @@ -79,6 +79,8 @@ [key: string]: Codec; }; configuration: { + appPromotionDailyRate: Perbill & AugmentedConst; + dayRelayBlocks: u32 & AugmentedConst; defaultMinGasPrice: u64 & AugmentedConst; defaultWeightToFeeCoefficient: u32 & AugmentedConst; maxOverridedAllowedLocations: u32 & AugmentedConst; --- a/tests/src/interfaces/augment-api-query.ts +++ b/tests/src/interfaces/augment-api-query.ts @@ -8,8 +8,13 @@ import type { ApiTypes, AugmentedQuery, QueryableStorageEntry } from '@polkadot/api-base/types'; import type { BTreeMap, Bytes, Option, U256, U8aFixed, Vec, bool, u128, u16, u32, u64, u8 } from '@polkadot/types-codec'; import type { AnyNumber, ITuple } from '@polkadot/types-codec/types'; +<<<<<<< HEAD import type { AccountId32, H160, H256 } from '@polkadot/types/interfaces/runtime'; import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueQueueConfigData, EthereumBlock, EthereumLog, EthereumReceiptReceiptV3, EthereumTransactionTransactionV2, FpRpcTransactionStatus, FrameSupportDispatchPerDispatchClassWeight, FrameSystemAccountInfo, FrameSystemEventRecord, FrameSystemLastRuntimeUpgradeInfo, FrameSystemPhase, OrmlTokensAccountData, OrmlTokensBalanceLock, OrmlTokensReserveData, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesReleases, PalletBalancesReserveData, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmContractHelpersSponsoringModeT, PalletForeignAssetsAssetIds, PalletForeignAssetsModuleAssetMetadata, PalletNonfungibleItemData, PalletRefungibleItemData, PalletTransactionPaymentReleases, PalletTreasuryProposal, PalletUniqueSchedulerV2BlockAgenda, PhantomTypeUpDataStructs, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpRuntimeDigest, SpTrieStorageProof, SpWeightsWeightV2Weight, UpDataStructsCollection, UpDataStructsCollectionStats, UpDataStructsProperties, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, XcmV1MultiLocation } from '@polkadot/types/lookup'; +======= +import type { AccountId32, H160, H256, Perbill, Weight } from '@polkadot/types/interfaces/runtime'; +import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueQueueConfigData, EthereumBlock, EthereumLog, EthereumReceiptReceiptV3, EthereumTransactionTransactionV2, FpRpcTransactionStatus, FrameSupportDispatchPerDispatchClassWeight, FrameSystemAccountInfo, FrameSystemEventRecord, FrameSystemLastRuntimeUpgradeInfo, FrameSystemPhase, OrmlTokensAccountData, OrmlTokensBalanceLock, OrmlTokensReserveData, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesReleases, PalletBalancesReserveData, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmContractHelpersSponsoringModeT, PalletForeignAssetsAssetIds, PalletForeignAssetsModuleAssetMetadata, PalletNonfungibleItemData, PalletRefungibleItemData, PalletTransactionPaymentReleases, PalletTreasuryProposal, PalletUniqueSchedulerV2BlockAgenda, PhantomTypeUpDataStructs, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpRuntimeDigest, SpTrieStorageProof, UpDataStructsCollection, UpDataStructsCollectionStats, UpDataStructsProperties, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, XcmV1MultiLocation } from '@polkadot/types/lookup'; +>>>>>>> added the ability to configure pallet `app-promotion` via the `configuration` palette import type { Observable } from '@polkadot/types/types'; export type __AugmentedQuery = AugmentedQuery unknown>; @@ -160,6 +165,7 @@ [key: string]: QueryableStorageEntry; }; configuration: { + appPromomotionConfigurationOverride: AugmentedQuery Observable, Option, Option, Option]>>>, []> & QueryableStorageEntry; minGasPriceOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; weightToFeeCoefficientOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; xcmAllowedLocationsOverride: AugmentedQuery Observable>>, []> & QueryableStorageEntry; --- a/tests/src/interfaces/augment-api-tx.ts +++ b/tests/src/interfaces/augment-api-tx.ts @@ -215,6 +215,7 @@ [key: string]: SubmittableExtrinsicFunction; }; configuration: { + setAppPromotionConfigurationOverride: AugmentedSubmittable<(recalculationInterval: Option | null | Uint8Array | u32 | AnyNumber, pendingInterval: Option | null | Uint8Array | u32 | AnyNumber, stakersPayoutLimit: Option | null | Uint8Array | u8 | AnyNumber) => SubmittableExtrinsic, [Option, Option, Option]>; setMinGasPriceOverride: AugmentedSubmittable<(coeff: Option | null | Uint8Array | u64 | AnyNumber) => SubmittableExtrinsic, [Option]>; setWeightToFeeCoefficientOverride: AugmentedSubmittable<(coeff: Option | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic, [Option]>; setXcmAllowedLocations: AugmentedSubmittable<(locations: Option> | null | Uint8Array | Vec | (XcmV1MultiLocation | { parents?: any; interior?: any } | string | Uint8Array)[]) => SubmittableExtrinsic, [Option>]>;