git.delta.rocks / unique-network / refs/commits / 1cc33de4c839

difftreelog

added the ability to configure pallet `app-promotion` via the `configuration` palette

PraetorP2022-12-13parent: #966149d.patch.diff
in: master

14 files changed

modifiedCargo.lockdiffbeforeafterboth
--- 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",
addedpallets/app-promotion/CHANGELOG.mddiffbeforeafterboth
--- /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.
+
+<!-- bureaucrate goes here -->
+
+## [0.1.1] - 2022-12-13
+
+### Added
+
+- The ability to configure pallet `app-promotion` via the `configuration` palette
modifiedpallets/app-promotion/Cargo.tomldiffbeforeafterboth
--- 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" }
modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
before · pallets/app-promotion/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # App Promotion pallet18//!19//! The pallet implements the mechanics of staking and sponsoring collections/contracts.20//!21//! - [`Config`]22//! - [`Pallet`]23//! - [`Error`]24//! - [`Event`]25//!26//! ## Overview27//! The App Promotion pallet allows fund holders to stake at a certain daily rate of return.28//! The mechanics implemented in the pallet allow it to act as a sponsor for collections / contracts,29//! the list of which is set by the pallet administrator.30//!31//!32//! ## Interface33//!	The pallet provides interfaces for funds, collection/contract operations (see [types] module).3435//!36//! ### Dispatchable Functions37//!	- [`set_admin_address`][`Pallet::set_admin_address`] - sets an address as the the admin.38//! - [`stake`][`Pallet::stake`] - stakes the amount of native tokens.39//! - [`unstake`][`Pallet::unstake`] - unstakes all stakes.40//! - [`sponsor_collection`][`Pallet::sponsor_collection`] - sets the pallet to be the sponsor for the collection.41//! - [`stop_sponsoring_collection`][`Pallet::stop_sponsoring_collection`] - removes the pallet as the sponsor for the collection.42//! - [`sponsor_contract`][`Pallet::sponsor_contract`] - sets the pallet to be the sponsor for the contract.43//! - [`stop_sponsoring_contract`][`Pallet::stop_sponsoring_contract`] - removes the pallet as the sponsor for the contract.44//! - [`payout_stakers`][`Pallet::payout_stakers`] - recalculates interest for the specified number of stakers.45//!4647// #![recursion_limit = "1024"]48#![cfg_attr(not(feature = "std"), no_std)]4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;5253pub mod types;54pub mod weights;5556use sp_std::{57	vec::{Vec},58	vec,59	iter::Sum,60	borrow::ToOwned,61	cell::RefCell,62};63use sp_core::H160;64use codec::EncodeLike;65use pallet_balances::BalanceLock;66pub use types::*;6768use up_data_structs::CollectionId;6970use frame_support::{71	dispatch::{DispatchResult},72	traits::{73		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,74	},75	ensure,76};7778use weights::WeightInfo;7980pub use pallet::*;81use pallet_evm::account::CrossAccountId;82use sp_runtime::{83	Perbill,84	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},85	ArithmeticError,86};8788pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";8990const PENDING_LIMIT_PER_BLOCK: u32 = 3;9192type BalanceOf<T> =93	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;9495#[frame_support::pallet]96pub mod pallet {97	use super::*;98	use frame_support::{99		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,100		traits::ReservableCurrency,101	};102	use frame_system::pallet_prelude::*;103104	#[pallet::config]105	pub trait Config: frame_system::Config + pallet_evm::Config {106		/// Type to interact with the native token107		type Currency: ExtendedLockableCurrency<Self::AccountId>108			+ ReservableCurrency<Self::AccountId>;109110		/// Type for interacting with collections111		type CollectionHandler: CollectionHandler<112			AccountId = Self::AccountId,113			CollectionId = CollectionId,114		>;115116		/// Type for interacting with conrtacts117		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;118119		/// `AccountId` for treasury120		type TreasuryAccountId: Get<Self::AccountId>;121122		/// The app's pallet id, used for deriving its sovereign account address.123		#[pallet::constant]124		type PalletId: Get<PalletId>;125126		/// In relay blocks.127		#[pallet::constant]128		type RecalculationInterval: Get<Self::BlockNumber>;129130		/// In parachain blocks.131		#[pallet::constant]132		type PendingInterval: Get<Self::BlockNumber>;133134		/// Rate of return for interval in blocks defined in `RecalculationInterval`.135		#[pallet::constant]136		type IntervalIncome: Get<Perbill>;137138		/// Decimals for the `Currency`.139		#[pallet::constant]140		type Nominal: Get<BalanceOf<Self>>;141142		/// Weight information for extrinsics in this pallet.143		type WeightInfo: WeightInfo;144145		// The relay block number provider146		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;147148		/// Events compatible with [`frame_system::Config::Event`].149		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;150	}151152	#[pallet::pallet]153	#[pallet::generate_store(pub(super) trait Store)]154	pub struct Pallet<T>(_);155156	#[pallet::event]157	#[pallet::generate_deposit(fn deposit_event)]158	pub enum Event<T: Config> {159		/// Staking recalculation was performed160		///161		/// # Arguments162		/// * AccountId: account of the staker.163		/// * Balance : recalculation base164		/// * Balance : total income165		StakingRecalculation(166			/// An recalculated staker167			T::AccountId,168			/// Base on which interest is calculated169			BalanceOf<T>,170			/// Amount of accrued interest171			BalanceOf<T>,172		),173174		/// Staking was performed175		///176		/// # Arguments177		/// * AccountId: account of the staker178		/// * Balance : staking amount179		Stake(T::AccountId, BalanceOf<T>),180181		/// Unstaking was performed182		///183		/// # Arguments184		/// * AccountId: account of the staker185		/// * Balance : unstaking amount186		Unstake(T::AccountId, BalanceOf<T>),187188		/// The admin was set189		///190		/// # Arguments191		/// * AccountId: account address of the admin192		SetAdmin(T::AccountId),193	}194195	#[pallet::error]196	pub enum Error<T> {197		/// Error due to action requiring admin to be set.198		AdminNotSet,199		/// No permission to perform an action.200		NoPermission,201		/// Insufficient funds to perform an action.202		NotSufficientFunds,203		/// Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.204		PendingForBlockOverflow,205		/// The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.206		SponsorNotSet,207		/// Errors caused by incorrect actions with a locked balance.208		IncorrectLockedBalanceOperation,209	}210211	/// Stores the total staked amount.212	#[pallet::storage]213	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;214215	/// Stores the `admin` account. Some extrinsics can only be executed if they were signed by `admin`.216	#[pallet::storage]217	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;218219	/// Stores the amount of tokens staked by account in the blocknumber.220	///221	/// * **Key1** - Staker account.222	/// * **Key2** - Relay block number when the stake was made.223	/// * **(Balance, BlockNumber)** - Balance of the stake.224	/// The number of the relay block in which we must perform the interest recalculation225	#[pallet::storage]226	pub type Staked<T: Config> = StorageNMap<227		Key = (228			Key<Blake2_128Concat, T::AccountId>,229			Key<Twox64Concat, T::BlockNumber>,230		),231		Value = (BalanceOf<T>, T::BlockNumber),232		QueryKind = ValueQuery,233	>;234235	/// Stores amount of stakes for an `Account`.236	///237	/// * **Key** - Staker account.238	/// * **Value** - Amount of stakes.239	#[pallet::storage]240	pub type StakesPerAccount<T: Config> =241		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;242243	/// Stores amount of stakes for an `Account`.244	///245	/// * **Key** - Staker account.246	/// * **Value** - Amount of stakes.247	#[pallet::storage]248	pub type PendingUnstake<T: Config> = StorageMap<249		_,250		Twox64Concat,251		T::BlockNumber,252		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,253		ValueQuery,254	>;255256	/// Stores a key for record for which the next revenue recalculation would be performed.257	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.258	#[pallet::storage]259	#[pallet::getter(fn get_next_calculated_record)]260	pub type NextCalculatedRecord<T: Config> =261		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;262263	#[pallet::hooks]264	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {265		/// Block overflow is impossible due to the fact that the unstake algorithm in on_initialize266		/// implies the execution of a strictly limited number of relatively lightweight operations.267		/// A separate benchmark has been implemented to scale the weight depending on the number of pendings.268		fn on_initialize(current_block_number: T::BlockNumber) -> Weight269		where270			<T as frame_system::Config>::BlockNumber: From<u32>,271		{272			let block_pending = PendingUnstake::<T>::take(current_block_number);273			let counter = block_pending.len() as u32;274275			if !block_pending.is_empty() {276				block_pending.into_iter().for_each(|(staker, amount)| {277					<<T as Config>::Currency as ReservableCurrency<T::AccountId>>::unreserve(278						&staker, amount,279					);280				});281			}282283			<T as Config>::WeightInfo::on_initialize(counter)284		}285	}286287	#[pallet::call]288	impl<T: Config> Pallet<T>289	where290		T::BlockNumber: From<u32> + Into<u32>,291		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,292	{293		/// Sets an address as the the admin.294		///295		/// # Permissions296		///297		/// * Sudo298		///299		/// # Arguments300		///301		/// * `admin`: account of the new admin.302		#[pallet::weight(<T as Config>::WeightInfo::set_admin_address())]303		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {304			ensure_root(origin)?;305306			<Admin<T>>::set(Some(admin.as_sub().to_owned()));307308			Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));309310			Ok(())311		}312313		/// Stakes the amount of native tokens.314		/// Sets `amount` to the locked state.315		/// The maximum number of stakes for a staker is 10.316		///317		/// # Arguments318		///319		/// * `amount`: in native tokens.320		#[pallet::weight(<T as Config>::WeightInfo::stake())]321		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {322			let staker_id = ensure_signed(staker)?;323324			ensure!(325				StakesPerAccount::<T>::get(&staker_id) < 10,326				Error::<T>::NoPermission327			);328329			ensure!(330				amount >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),331				ArithmeticError::Underflow332			);333334			let balance =335				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);336337			// checks that we can lock `amount` on the `staker` account.338			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(339				&staker_id,340				amount,341				WithdrawReasons::all(),342				balance343					.checked_sub(&amount)344					.ok_or(ArithmeticError::Underflow)?,345			)?;346347			Self::add_lock_balance(&staker_id, amount)?;348349			let block_number = T::RelayBlockNumberProvider::current_block_number();350351			// Calculation of the number of recalculation periods,352			// after how much the first interest calculation should be performed for the stake353			let recalculate_after_interval: T::BlockNumber =354				if block_number % T::RecalculationInterval::get() == 0u32.into() {355					1u32.into()356				} else {357					2u32.into()358				};359360			// Сalculation of the number of the relay block361			// in which it is necessary to accrue remuneration for the stake.362			let recalc_block = (block_number / T::RecalculationInterval::get()363				+ recalculate_after_interval)364				* T::RecalculationInterval::get();365366			<Staked<T>>::insert((&staker_id, block_number), {367				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));368				balance_and_recalc_block.0 = balance_and_recalc_block369					.0370					.checked_add(&amount)371					.ok_or(ArithmeticError::Overflow)?;372				balance_and_recalc_block.1 = recalc_block;373				balance_and_recalc_block374			});375376			<TotalStaked<T>>::set(377				<TotalStaked<T>>::get()378					.checked_add(&amount)379					.ok_or(ArithmeticError::Overflow)?,380			);381382			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);383384			Self::deposit_event(Event::Stake(staker_id, amount));385386			Ok(())387		}388389		/// Unstakes all stakes.390		/// Moves the sum of all stakes to the `reserved` state.391		/// After the end of `PendingInterval` this sum becomes completely392		/// free for further use.393		#[pallet::weight(<T as Config>::WeightInfo::unstake())]394		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {395			let staker_id = ensure_signed(staker)?;396397			// calculate block number where the sum would be free398			let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();399400			let mut pendings = <PendingUnstake<T>>::get(block);401402			// checks that we can do unreserve stakes in the block403			ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);404405			let mut total_stakes = 0u64;406407			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))408				.map(|(_, (amount, _))| {409					total_stakes += 1;410					amount411				})412				.sum();413414			if total_staked.is_zero() {415				return Ok(None::<Weight>.into()); // TO-DO416			}417418			pendings419				.try_push((staker_id.clone(), total_staked))420				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;421422			<PendingUnstake<T>>::insert(block, pendings);423424			Self::unlock_balance(&staker_id, total_staked)?;425426			<<T as Config>::Currency as ReservableCurrency<T::AccountId>>::reserve(427				&staker_id,428				total_staked,429			)?;430431			TotalStaked::<T>::set(432				TotalStaked::<T>::get()433					.checked_sub(&total_staked)434					.ok_or(ArithmeticError::Underflow)?,435			);436437			StakesPerAccount::<T>::remove(&staker_id);438439			Self::deposit_event(Event::Unstake(staker_id, total_staked));440441			Ok(None::<Weight>.into())442		}443444		/// Sets the pallet to be the sponsor for the collection.445		///446		/// # Permissions447		///448		/// * Pallet admin449		///450		/// # Arguments451		///452		/// * `collection_id`: ID of the collection that will be sponsored by `pallet_id`453		#[pallet::weight(<T as Config>::WeightInfo::sponsor_collection())]454		pub fn sponsor_collection(455			admin: OriginFor<T>,456			collection_id: CollectionId,457		) -> DispatchResult {458			let admin_id = ensure_signed(admin)?;459			ensure!(460				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,461				Error::<T>::NoPermission462			);463464			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)465		}466467		/// Removes the pallet as the sponsor for the collection.468		/// Returns [`NoPermission`][`Error::NoPermission`]469		/// if the pallet wasn't the sponsor.470		///471		/// # Permissions472		///473		/// * Pallet admin474		///475		/// # Arguments476		///477		/// * `collection_id`: ID of the collection that is sponsored by `pallet_id`478		#[pallet::weight(<T as Config>::WeightInfo::stop_sponsoring_collection())]479		pub fn stop_sponsoring_collection(480			admin: OriginFor<T>,481			collection_id: CollectionId,482		) -> DispatchResult {483			let admin_id = ensure_signed(admin)?;484485			ensure!(486				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,487				Error::<T>::NoPermission488			);489490			ensure!(491				T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?492					== Self::account_id(),493				<Error<T>>::NoPermission494			);495			T::CollectionHandler::remove_collection_sponsor(collection_id)496		}497498		/// Sets the pallet to be the sponsor for the contract.499		///500		/// # Permissions501		///502		/// * Pallet admin503		///504		/// # Arguments505		///506		/// * `contract_id`: the contract address that will be sponsored by `pallet_id`507		#[pallet::weight(<T as Config>::WeightInfo::sponsor_contract())]508		pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {509			let admin_id = ensure_signed(admin)?;510511			ensure!(512				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,513				Error::<T>::NoPermission514			);515516			T::ContractHandler::set_sponsor(517				T::CrossAccountId::from_sub(Self::account_id()),518				contract_id,519			)520		}521522		/// Removes the pallet as the sponsor for the contract.523		/// Returns [`NoPermission`][`Error::NoPermission`]524		/// if the pallet wasn't the sponsor.525		///526		/// # Permissions527		///528		/// * Pallet admin529		///530		/// # Arguments531		///532		/// * `contract_id`: the contract address that is sponsored by `pallet_id`533		#[pallet::weight(<T as Config>::WeightInfo::stop_sponsoring_contract())]534		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {535			let admin_id = ensure_signed(admin)?;536537			ensure!(538				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,539				Error::<T>::NoPermission540			);541542			ensure!(543				T::ContractHandler::sponsor(contract_id)?544					.ok_or(<Error<T>>::SponsorNotSet)?545					.as_sub() == &Self::account_id(),546				<Error<T>>::NoPermission547			);548			T::ContractHandler::remove_contract_sponsor(contract_id)549		}550551		/// Recalculates interest for the specified number of stakers.552		/// If all stakers are not recalculated, the next call of the extrinsic553		/// will continue the recalculation, from those stakers for whom this554		/// was not perform in last call.555		///556		/// # Permissions557		///558		/// * Pallet admin559		///560		/// # Arguments561		///562		/// * `stakers_number`: the number of stakers for which recalculation will be performed563		#[pallet::weight(<T as Config>::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]564		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {565			let admin_id = ensure_signed(admin)?;566567			ensure!(568				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,569				Error::<T>::NoPermission570			);571572			// calculate the number of the current recalculation block,573			// this is necessary in order to understand which stakers we should calculate interest574			let current_recalc_block =575				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());576577			// calculate the number of the next recalculation block,578			// this value is set for the stakers to whom the recalculation will be performed579			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();580581			let mut storage_iterator = Self::get_next_calculated_key()582				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));583584			NextCalculatedRecord::<T>::set(None);585586			{587				let mut stakers_number = stakers_number.unwrap_or(20);588				let last_id = RefCell::new(None);589				let income_acc = RefCell::new(BalanceOf::<T>::default());590				let amount_acc = RefCell::new(BalanceOf::<T>::default());591592				// this closure is used to perform some of the actions if we break the loop because we reached the number of stakers for recalculation,593				// but there were unrecalculated records in the storage.594				let flush_stake = || -> DispatchResult {595					if let Some(last_id) = &*last_id.borrow() {596						if !income_acc.borrow().is_zero() {597							<<T as Config>::Currency as Currency<T::AccountId>>::transfer(598								&T::TreasuryAccountId::get(),599								last_id,600								*income_acc.borrow(),601								ExistenceRequirement::KeepAlive,602							)?;603604							Self::add_lock_balance(last_id, *income_acc.borrow())?;605							<TotalStaked<T>>::try_mutate(|staked| -> DispatchResult {606								*staked = staked607									.checked_add(&*income_acc.borrow())608									.ok_or(ArithmeticError::Overflow)?;609								Ok(())610							})?;611612							Self::deposit_event(Event::StakingRecalculation(613								last_id.clone(),614								*amount_acc.borrow(),615								*income_acc.borrow(),616							));617						}618619						*income_acc.borrow_mut() = BalanceOf::<T>::default();620						*amount_acc.borrow_mut() = BalanceOf::<T>::default();621					}622					Ok(())623				};624625				while let Some((626					(current_id, staked_block),627					(amount, next_recalc_block_for_stake),628				)) = storage_iterator.next()629				{630					if stakers_number == 0 {631						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));632						break;633					}634					if last_id.borrow().as_ref() != Some(&current_id) {635						flush_stake()?;636						*last_id.borrow_mut() = Some(current_id.clone());637						stakers_number -= 1;638					};639					if current_recalc_block >= next_recalc_block_for_stake {640						*amount_acc.borrow_mut() += amount;641						Self::recalculate_and_insert_stake(642							&current_id,643							staked_block,644							next_recalc_block,645							amount,646							((current_recalc_block - next_recalc_block_for_stake)647								/ T::RecalculationInterval::get())648							.into() + 1,649							&mut *income_acc.borrow_mut(),650						);651					}652				}653				flush_stake()?;654			}655656			Ok(())657		}658	}659}660661impl<T: Config> Pallet<T> {662	/// The account address of the app promotion pot.663	///664	/// This actually does computation. If you need to keep using it, then make sure you cache the665	/// value and only call this once.666	pub fn account_id() -> T::AccountId {667		T::PalletId::get().into_account_truncating()668	}669670	/// Unlocks the balance that was locked by the pallet.671	///672	/// - `staker`: staker account.673	/// - `amount`: amount of unlocked funds.674	fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {675		let locked_balance = Self::get_locked_balance(staker)676			.map(|l| l.amount)677			.ok_or(<Error<T>>::IncorrectLockedBalanceOperation)?;678679		// It is understood that we cannot unlock more funds than were locked by staking.680		// Therefore, if implemented correctly, this error should not occur.681		Self::set_lock_unchecked(682			staker,683			locked_balance684				.checked_sub(&amount)685				.ok_or(ArithmeticError::Underflow)?,686		);687		Ok(())688	}689690	/// Adds the balance to locked by the pallet.691	///692	/// - `staker`: staker account.693	/// - `amount`: amount of added locked funds.694	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {695		Self::get_locked_balance(staker)696			.map_or(<BalanceOf<T>>::default(), |l| l.amount)697			.checked_add(&amount)698			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))699			.ok_or(ArithmeticError::Overflow.into())700	}701702	/// Sets the new state of a balance locked by the pallet.703	///704	/// - `staker`: staker account.705	/// - `amount`: amount of locked funds.706	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {707		if amount.is_zero() {708			<<T as Config>::Currency as LockableCurrency<T::AccountId>>::remove_lock(709				LOCK_IDENTIFIER,710				&staker,711			);712		} else {713			<<T as Config>::Currency as LockableCurrency<T::AccountId>>::set_lock(714				LOCK_IDENTIFIER,715				staker,716				amount,717				WithdrawReasons::all(),718			)719		}720	}721722	/// Returns the balance locked by the pallet for the staker.723	///724	/// - `staker`: staker account.725	pub fn get_locked_balance(726		staker: impl EncodeLike<T::AccountId>,727	) -> Option<BalanceLock<BalanceOf<T>>> {728		<<T as Config>::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)729			.into_iter()730			.find(|l| l.id == LOCK_IDENTIFIER)731	}732733	/// Returns the total staked balance for the staker.734	///735	/// - `staker`: staker account.736	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {737		let staked = Staked::<T>::iter_prefix((staker,))738			.into_iter()739			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {740				acc + amount741			});742		if staked != <BalanceOf<T>>::default() {743			Some(staked)744		} else {745			None746		}747	}748749	/// Returns all relay block numbers when stake was made,750	/// the amount of the stake.751	///752	/// - `staker`: staker account.753	pub fn total_staked_by_id_per_block(754		staker: impl EncodeLike<T::AccountId>,755	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {756		let mut staked = Staked::<T>::iter_prefix((staker,))757			.into_iter()758			.map(|(block, (amount, _))| (block, amount))759			.collect::<Vec<_>>();760		staked.sort_by_key(|(block, _)| *block);761		if !staked.is_empty() {762			Some(staked)763		} else {764			None765		}766	}767768	/// Returns the total staked balance for the staker.769	/// If `staker` is `None`, returns the total amount staked.770	/// - `staker`: staker account.771	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {772		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {773			Self::total_staked_by_id(s.as_sub())774		})775	}776777	// pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {778	// 	Self::get_locked_balance(staker.as_sub())779	// 		.map(|l| l.amount)780	// 		.unwrap_or_default()781	// }782783	/// Returns all relay block numbers when stake was made,784	/// the amount of the stake.785	///786	/// - `staker`: staker account.787	pub fn cross_id_total_staked_per_block(788		staker: T::CrossAccountId,789	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {790		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()791	}792793	fn recalculate_and_insert_stake(794		staker: &T::AccountId,795		staked_block: T::BlockNumber,796		next_recalc_block: T::BlockNumber,797		base: BalanceOf<T>,798		iters: u32,799		income_acc: &mut BalanceOf<T>,800	) {801		let income = Self::calculate_income(base, iters);802803		base.checked_add(&income).map(|res| {804			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));805			*income_acc += income;806		});807	}808809	fn calculate_income<I>(base: I, iters: u32) -> I810	where811		I: EncodeLike<BalanceOf<T>> + Balance,812	{813		let mut income = base;814815		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);816817		income - base818	}819820	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {821		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()822	}823824	fn get_next_calculated_key() -> Option<Vec<u8>> {825		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))826	}827}828829impl<T: Config> Pallet<T>830where831	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,832{833	/// Returns the amount reserved by the pending.834	/// If `staker` is `None`, returns the total pending.835	///836	/// -`staker`: staker account.837	///838	/// Since user funds are not transferred anywhere by staking, overflow protection is provided839	/// at the level of the associated type `Balance` of `Currency` trait. In order to overflow,840	/// the staker must have more funds on his account than the maximum set for `Balance` type.841	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {842		staker.map_or(843			PendingUnstake::<T>::iter_values()844				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))845				.sum(),846			|s| {847				PendingUnstake::<T>::iter_values()848					.flatten()849					.filter_map(|(id, amount)| {850						if id == *s.as_sub() {851							Some(amount)852						} else {853							None854						}855					})856					.sum()857			},858		)859	}860861	/// Returns all parachain block numbers when unreserve is expected,862	/// the amount of the unreserved funds.863	///864	/// - `staker`: staker account.865	pub fn cross_id_pending_unstake_per_block(866		staker: T::CrossAccountId,867	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {868		let mut unsorted_res = vec![];869		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {870			pendings.into_iter().for_each(|(id, amount)| {871				if id == *staker.as_sub() {872					unsorted_res.push((block, amount));873				};874			})875		});876877		unsorted_res.sort_by_key(|(block, _)| *block);878		unsorted_res879	}880}
after · pallets/app-promotion/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # App Promotion pallet18//!19//! The pallet implements the mechanics of staking and sponsoring collections/contracts.20//!21//! - [`Config`]22//! - [`Pallet`]23//! - [`Error`]24//! - [`Event`]25//!26//! ## Overview27//! The App Promotion pallet allows fund holders to stake at a certain daily rate of return.28//! The mechanics implemented in the pallet allow it to act as a sponsor for collections / contracts,29//! the list of which is set by the pallet administrator.30//!31//!32//! ## Interface33//!	The pallet provides interfaces for funds, collection/contract operations (see [types] module).3435//!36//! ### Dispatchable Functions37//!	- [`set_admin_address`][`Pallet::set_admin_address`] - sets an address as the the admin.38//! - [`stake`][`Pallet::stake`] - stakes the amount of native tokens.39//! - [`unstake`][`Pallet::unstake`] - unstakes all stakes.40//! - [`sponsor_collection`][`Pallet::sponsor_collection`] - sets the pallet to be the sponsor for the collection.41//! - [`stop_sponsoring_collection`][`Pallet::stop_sponsoring_collection`] - removes the pallet as the sponsor for the collection.42//! - [`sponsor_contract`][`Pallet::sponsor_contract`] - sets the pallet to be the sponsor for the contract.43//! - [`stop_sponsoring_contract`][`Pallet::stop_sponsoring_contract`] - removes the pallet as the sponsor for the contract.44//! - [`payout_stakers`][`Pallet::payout_stakers`] - recalculates interest for the specified number of stakers.45//!4647// #![recursion_limit = "1024"]48#![cfg_attr(not(feature = "std"), no_std)]4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;5253pub mod types;54pub mod weights;5556use sp_std::{57	vec::{Vec},58	vec,59	iter::Sum,60	borrow::ToOwned,61	cell::RefCell,62};63use sp_core::H160;64use codec::EncodeLike;65use pallet_balances::BalanceLock;66pub use types::*;6768use up_data_structs::CollectionId;6970use frame_support::{71	dispatch::{DispatchResult},72	traits::{73		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,74	},75	ensure,76};7778use weights::WeightInfo;7980pub use pallet::*;81use pallet_evm::account::CrossAccountId;82use sp_runtime::{83	Perbill,84	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},85	ArithmeticError,86};8788pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";8990const PENDING_LIMIT_PER_BLOCK: u32 = 3;9192type BalanceOf<T> =93	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;9495#[frame_support::pallet]96pub mod pallet {97	use super::*;98	use frame_support::{99		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,100		traits::ReservableCurrency,101	};102	use frame_system::pallet_prelude::*;103104	#[pallet::config]105	pub trait Config:106		frame_system::Config + pallet_evm::Config + pallet_configuration::Config107	{108		/// Type to interact with the native token109		type Currency: ExtendedLockableCurrency<Self::AccountId>110			+ ReservableCurrency<Self::AccountId>;111112		/// Type for interacting with collections113		type CollectionHandler: CollectionHandler<114			AccountId = Self::AccountId,115			CollectionId = CollectionId,116		>;117118		/// Type for interacting with conrtacts119		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;120121		/// `AccountId` for treasury122		type TreasuryAccountId: Get<Self::AccountId>;123124		/// The app's pallet id, used for deriving its sovereign account address.125		#[pallet::constant]126		type PalletId: Get<PalletId>;127128		/// In relay blocks.129		#[pallet::constant]130		type RecalculationInterval: Get<Self::BlockNumber>;131132		/// In parachain blocks.133		#[pallet::constant]134		type PendingInterval: Get<Self::BlockNumber>;135136		/// Rate of return for interval in blocks defined in `RecalculationInterval`.137		#[pallet::constant]138		type IntervalIncome: Get<Perbill>;139140		/// Decimals for the `Currency`.141		#[pallet::constant]142		type Nominal: Get<BalanceOf<Self>>;143144		/// Weight information for extrinsics in this pallet.145		type WeightInfo: WeightInfo;146147		// The relay block number provider148		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;149150		/// Events compatible with [`frame_system::Config::Event`].151		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;152	}153154	#[pallet::pallet]155	#[pallet::generate_store(pub(super) trait Store)]156	pub struct Pallet<T>(_);157158	#[pallet::event]159	#[pallet::generate_deposit(fn deposit_event)]160	pub enum Event<T: Config> {161		/// Staking recalculation was performed162		///163		/// # Arguments164		/// * AccountId: account of the staker.165		/// * Balance : recalculation base166		/// * Balance : total income167		StakingRecalculation(168			/// An recalculated staker169			T::AccountId,170			/// Base on which interest is calculated171			BalanceOf<T>,172			/// Amount of accrued interest173			BalanceOf<T>,174		),175176		/// Staking was performed177		///178		/// # Arguments179		/// * AccountId: account of the staker180		/// * Balance : staking amount181		Stake(T::AccountId, BalanceOf<T>),182183		/// Unstaking was performed184		///185		/// # Arguments186		/// * AccountId: account of the staker187		/// * Balance : unstaking amount188		Unstake(T::AccountId, BalanceOf<T>),189190		/// The admin was set191		///192		/// # Arguments193		/// * AccountId: account address of the admin194		SetAdmin(T::AccountId),195	}196197	#[pallet::error]198	pub enum Error<T> {199		/// Error due to action requiring admin to be set.200		AdminNotSet,201		/// No permission to perform an action.202		NoPermission,203		/// Insufficient funds to perform an action.204		NotSufficientFunds,205		/// Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.206		PendingForBlockOverflow,207		/// The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.208		SponsorNotSet,209		/// Errors caused by incorrect actions with a locked balance.210		IncorrectLockedBalanceOperation,211	}212213	/// Stores the total staked amount.214	#[pallet::storage]215	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;216217	/// Stores the `admin` account. Some extrinsics can only be executed if they were signed by `admin`.218	#[pallet::storage]219	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;220221	/// Stores the amount of tokens staked by account in the blocknumber.222	///223	/// * **Key1** - Staker account.224	/// * **Key2** - Relay block number when the stake was made.225	/// * **(Balance, BlockNumber)** - Balance of the stake.226	/// The number of the relay block in which we must perform the interest recalculation227	#[pallet::storage]228	pub type Staked<T: Config> = StorageNMap<229		Key = (230			Key<Blake2_128Concat, T::AccountId>,231			Key<Twox64Concat, T::BlockNumber>,232		),233		Value = (BalanceOf<T>, T::BlockNumber),234		QueryKind = ValueQuery,235	>;236237	/// Stores amount of stakes for an `Account`.238	///239	/// * **Key** - Staker account.240	/// * **Value** - Amount of stakes.241	#[pallet::storage]242	pub type StakesPerAccount<T: Config> =243		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;244245	/// Stores amount of stakes for an `Account`.246	///247	/// * **Key** - Staker account.248	/// * **Value** - Amount of stakes.249	#[pallet::storage]250	pub type PendingUnstake<T: Config> = StorageMap<251		_,252		Twox64Concat,253		T::BlockNumber,254		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,255		ValueQuery,256	>;257258	/// Stores a key for record for which the next revenue recalculation would be performed.259	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.260	#[pallet::storage]261	#[pallet::getter(fn get_next_calculated_record)]262	pub type NextCalculatedRecord<T: Config> =263		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;264265	#[pallet::hooks]266	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {267		/// Block overflow is impossible due to the fact that the unstake algorithm in on_initialize268		/// implies the execution of a strictly limited number of relatively lightweight operations.269		/// A separate benchmark has been implemented to scale the weight depending on the number of pendings.270		fn on_initialize(current_block_number: T::BlockNumber) -> Weight271		where272			<T as frame_system::Config>::BlockNumber: From<u32>,273		{274			let block_pending = PendingUnstake::<T>::take(current_block_number);275			let counter = block_pending.len() as u32;276277			if !block_pending.is_empty() {278				block_pending.into_iter().for_each(|(staker, amount)| {279					<<T as Config>::Currency as ReservableCurrency<T::AccountId>>::unreserve(280						&staker, amount,281					);282				});283			}284285			<T as Config>::WeightInfo::on_initialize(counter)286		}287	}288289	#[pallet::call]290	impl<T: Config> Pallet<T>291	where292		T::BlockNumber: From<u32> + Into<u32>,293		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,294	{295		/// Sets an address as the the admin.296		///297		/// # Permissions298		///299		/// * Sudo300		///301		/// # Arguments302		///303		/// * `admin`: account of the new admin.304		#[pallet::weight(<T as Config>::WeightInfo::set_admin_address())]305		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {306			ensure_root(origin)?;307308			<Admin<T>>::set(Some(admin.as_sub().to_owned()));309310			Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));311312			Ok(())313		}314315		/// Stakes the amount of native tokens.316		/// Sets `amount` to the locked state.317		/// The maximum number of stakes for a staker is 10.318		///319		/// # Arguments320		///321		/// * `amount`: in native tokens.322		#[pallet::weight(<T as Config>::WeightInfo::stake())]323		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {324			let staker_id = ensure_signed(staker)?;325326			ensure!(327				StakesPerAccount::<T>::get(&staker_id) < 10,328				Error::<T>::NoPermission329			);330331			ensure!(332				amount >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),333				ArithmeticError::Underflow334			);335			let config = <PalletConfiguration<T>>::get();336337			let balance =338				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);339340			// checks that we can lock `amount` on the `staker` account.341			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(342				&staker_id,343				amount,344				WithdrawReasons::all(),345				balance346					.checked_sub(&amount)347					.ok_or(ArithmeticError::Underflow)?,348			)?;349350			Self::add_lock_balance(&staker_id, amount)?;351352			let block_number = T::RelayBlockNumberProvider::current_block_number();353354			// Calculation of the number of recalculation periods,355			// after how much the first interest calculation should be performed for the stake356			let recalculate_after_interval: T::BlockNumber =357				if block_number % config.recalculation_interval == 0u32.into() {358					1u32.into()359				} else {360					2u32.into()361				};362363			// Сalculation of the number of the relay block364			// in which it is necessary to accrue remuneration for the stake.365			let recalc_block = (block_number / config.recalculation_interval366				+ recalculate_after_interval)367				* config.recalculation_interval;368369			<Staked<T>>::insert((&staker_id, block_number), {370				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));371				balance_and_recalc_block.0 = balance_and_recalc_block372					.0373					.checked_add(&amount)374					.ok_or(ArithmeticError::Overflow)?;375				balance_and_recalc_block.1 = recalc_block;376				balance_and_recalc_block377			});378379			<TotalStaked<T>>::set(380				<TotalStaked<T>>::get()381					.checked_add(&amount)382					.ok_or(ArithmeticError::Overflow)?,383			);384385			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);386387			Self::deposit_event(Event::Stake(staker_id, amount));388389			Ok(())390		}391392		/// Unstakes all stakes.393		/// Moves the sum of all stakes to the `reserved` state.394		/// After the end of `PendingInterval` this sum becomes completely395		/// free for further use.396		#[pallet::weight(<T as Config>::WeightInfo::unstake())]397		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {398			let staker_id = ensure_signed(staker)?;399			let config = <PalletConfiguration<T>>::get();400401			// calculate block number where the sum would be free402			let block = <frame_system::Pallet<T>>::block_number() + config.pending_interval;403404			let mut pendings = <PendingUnstake<T>>::get(block);405406			// checks that we can do unreserve stakes in the block407			ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);408409			let mut total_stakes = 0u64;410411			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))412				.map(|(_, (amount, _))| {413					total_stakes += 1;414					amount415				})416				.sum();417418			if total_staked.is_zero() {419				return Ok(None::<Weight>.into()); // TO-DO420			}421422			pendings423				.try_push((staker_id.clone(), total_staked))424				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;425426			<PendingUnstake<T>>::insert(block, pendings);427428			Self::unlock_balance(&staker_id, total_staked)?;429430			<<T as Config>::Currency as ReservableCurrency<T::AccountId>>::reserve(431				&staker_id,432				total_staked,433			)?;434435			TotalStaked::<T>::set(436				TotalStaked::<T>::get()437					.checked_sub(&total_staked)438					.ok_or(ArithmeticError::Underflow)?,439			);440441			StakesPerAccount::<T>::remove(&staker_id);442443			Self::deposit_event(Event::Unstake(staker_id, total_staked));444445			Ok(None::<Weight>.into())446		}447448		/// Sets the pallet to be the sponsor for the collection.449		///450		/// # Permissions451		///452		/// * Pallet admin453		///454		/// # Arguments455		///456		/// * `collection_id`: ID of the collection that will be sponsored by `pallet_id`457		#[pallet::weight(<T as Config>::WeightInfo::sponsor_collection())]458		pub fn sponsor_collection(459			admin: OriginFor<T>,460			collection_id: CollectionId,461		) -> DispatchResult {462			let admin_id = ensure_signed(admin)?;463			ensure!(464				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,465				Error::<T>::NoPermission466			);467468			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)469		}470471		/// Removes the pallet as the sponsor for the collection.472		/// Returns [`NoPermission`][`Error::NoPermission`]473		/// if the pallet wasn't the sponsor.474		///475		/// # Permissions476		///477		/// * Pallet admin478		///479		/// # Arguments480		///481		/// * `collection_id`: ID of the collection that is sponsored by `pallet_id`482		#[pallet::weight(<T as Config>::WeightInfo::stop_sponsoring_collection())]483		pub fn stop_sponsoring_collection(484			admin: OriginFor<T>,485			collection_id: CollectionId,486		) -> DispatchResult {487			let admin_id = ensure_signed(admin)?;488489			ensure!(490				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,491				Error::<T>::NoPermission492			);493494			ensure!(495				T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?496					== Self::account_id(),497				<Error<T>>::NoPermission498			);499			T::CollectionHandler::remove_collection_sponsor(collection_id)500		}501502		/// Sets the pallet to be the sponsor for the contract.503		///504		/// # Permissions505		///506		/// * Pallet admin507		///508		/// # Arguments509		///510		/// * `contract_id`: the contract address that will be sponsored by `pallet_id`511		#[pallet::weight(<T as Config>::WeightInfo::sponsor_contract())]512		pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {513			let admin_id = ensure_signed(admin)?;514515			ensure!(516				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,517				Error::<T>::NoPermission518			);519520			T::ContractHandler::set_sponsor(521				T::CrossAccountId::from_sub(Self::account_id()),522				contract_id,523			)524		}525526		/// Removes the pallet as the sponsor for the contract.527		/// Returns [`NoPermission`][`Error::NoPermission`]528		/// if the pallet wasn't the sponsor.529		///530		/// # Permissions531		///532		/// * Pallet admin533		///534		/// # Arguments535		///536		/// * `contract_id`: the contract address that is sponsored by `pallet_id`537		#[pallet::weight(<T as Config>::WeightInfo::stop_sponsoring_contract())]538		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {539			let admin_id = ensure_signed(admin)?;540541			ensure!(542				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,543				Error::<T>::NoPermission544			);545546			ensure!(547				T::ContractHandler::sponsor(contract_id)?548					.ok_or(<Error<T>>::SponsorNotSet)?549					.as_sub() == &Self::account_id(),550				<Error<T>>::NoPermission551			);552			T::ContractHandler::remove_contract_sponsor(contract_id)553		}554555		/// Recalculates interest for the specified number of stakers.556		/// If all stakers are not recalculated, the next call of the extrinsic557		/// will continue the recalculation, from those stakers for whom this558		/// was not perform in last call.559		///560		/// # Permissions561		///562		/// * Pallet admin563		///564		/// # Arguments565		///566		/// * `stakers_number`: the number of stakers for which recalculation will be performed567		#[pallet::weight(<T as Config>::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]568		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {569			let admin_id = ensure_signed(admin)?;570571			ensure!(572				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,573				Error::<T>::NoPermission574			);575			let config = <PalletConfiguration<T>>::get();576577			let mut stakers_number = stakers_number.unwrap_or(DEFAULT_NUMBER_PAYOUTS);578579			ensure!(580				stakers_number <= config.max_stakers_per_calculation,581				Error::<T>::NoPermission582			);583584			// calculate the number of the current recalculation block,585			// this is necessary in order to understand which stakers we should calculate interest586			let current_recalc_block = Self::get_current_recalc_block(587				T::RelayBlockNumberProvider::current_block_number(),588				&config,589			);590591			// calculate the number of the next recalculation block,592			// this value is set for the stakers to whom the recalculation will be performed593			let next_recalc_block = current_recalc_block + config.recalculation_interval;594595			let mut storage_iterator = Self::get_next_calculated_key()596				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));597598			NextCalculatedRecord::<T>::set(None);599600			{601				let last_id = RefCell::new(None);602				let income_acc = RefCell::new(BalanceOf::<T>::default());603				let amount_acc = RefCell::new(BalanceOf::<T>::default());604605				// this closure is used to perform some of the actions if we break the loop because we reached the number of stakers for recalculation,606				// but there were unrecalculated records in the storage.607				let flush_stake = || -> DispatchResult {608					if let Some(last_id) = &*last_id.borrow() {609						if !income_acc.borrow().is_zero() {610							<<T as Config>::Currency as Currency<T::AccountId>>::transfer(611								&T::TreasuryAccountId::get(),612								last_id,613								*income_acc.borrow(),614								ExistenceRequirement::KeepAlive,615							)?;616617							Self::add_lock_balance(last_id, *income_acc.borrow())?;618							<TotalStaked<T>>::try_mutate(|staked| -> DispatchResult {619								*staked = staked620									.checked_add(&*income_acc.borrow())621									.ok_or(ArithmeticError::Overflow)?;622								Ok(())623							})?;624625							Self::deposit_event(Event::StakingRecalculation(626								last_id.clone(),627								*amount_acc.borrow(),628								*income_acc.borrow(),629							));630						}631632						*income_acc.borrow_mut() = BalanceOf::<T>::default();633						*amount_acc.borrow_mut() = BalanceOf::<T>::default();634					}635					Ok(())636				};637638				while let Some((639					(current_id, staked_block),640					(amount, next_recalc_block_for_stake),641				)) = storage_iterator.next()642				{643					if stakers_number == 0 {644						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));645						break;646					}647					if last_id.borrow().as_ref() != Some(&current_id) {648						flush_stake()?;649						*last_id.borrow_mut() = Some(current_id.clone());650						stakers_number -= 1;651					};652					if current_recalc_block >= next_recalc_block_for_stake {653						*amount_acc.borrow_mut() += amount;654						Self::recalculate_and_insert_stake(655							&current_id,656							staked_block,657							next_recalc_block,658							amount,659							((current_recalc_block - next_recalc_block_for_stake)660								/ config.recalculation_interval)661								.into() + 1,662							&mut *income_acc.borrow_mut(),663						);664					}665				}666				flush_stake()?;667			}668669			Ok(())670		}671	}672}673674impl<T: Config> Pallet<T> {675	/// The account address of the app promotion pot.676	///677	/// This actually does computation. If you need to keep using it, then make sure you cache the678	/// value and only call this once.679	pub fn account_id() -> T::AccountId {680		T::PalletId::get().into_account_truncating()681	}682683	/// Unlocks the balance that was locked by the pallet.684	///685	/// - `staker`: staker account.686	/// - `amount`: amount of unlocked funds.687	fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {688		let locked_balance = Self::get_locked_balance(staker)689			.map(|l| l.amount)690			.ok_or(<Error<T>>::IncorrectLockedBalanceOperation)?;691692		// It is understood that we cannot unlock more funds than were locked by staking.693		// Therefore, if implemented correctly, this error should not occur.694		Self::set_lock_unchecked(695			staker,696			locked_balance697				.checked_sub(&amount)698				.ok_or(ArithmeticError::Underflow)?,699		);700		Ok(())701	}702703	/// Adds the balance to locked by the pallet.704	///705	/// - `staker`: staker account.706	/// - `amount`: amount of added locked funds.707	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {708		Self::get_locked_balance(staker)709			.map_or(<BalanceOf<T>>::default(), |l| l.amount)710			.checked_add(&amount)711			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))712			.ok_or(ArithmeticError::Overflow.into())713	}714715	/// Sets the new state of a balance locked by the pallet.716	///717	/// - `staker`: staker account.718	/// - `amount`: amount of locked funds.719	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {720		if amount.is_zero() {721			<<T as Config>::Currency as LockableCurrency<T::AccountId>>::remove_lock(722				LOCK_IDENTIFIER,723				&staker,724			);725		} else {726			<<T as Config>::Currency as LockableCurrency<T::AccountId>>::set_lock(727				LOCK_IDENTIFIER,728				staker,729				amount,730				WithdrawReasons::all(),731			)732		}733	}734735	/// Returns the balance locked by the pallet for the staker.736	///737	/// - `staker`: staker account.738	pub fn get_locked_balance(739		staker: impl EncodeLike<T::AccountId>,740	) -> Option<BalanceLock<BalanceOf<T>>> {741		<<T as Config>::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)742			.into_iter()743			.find(|l| l.id == LOCK_IDENTIFIER)744	}745746	/// Returns the total staked balance for the staker.747	///748	/// - `staker`: staker account.749	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {750		let staked = Staked::<T>::iter_prefix((staker,))751			.into_iter()752			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {753				acc + amount754			});755		if staked != <BalanceOf<T>>::default() {756			Some(staked)757		} else {758			None759		}760	}761762	/// Returns all relay block numbers when stake was made,763	/// the amount of the stake.764	///765	/// - `staker`: staker account.766	pub fn total_staked_by_id_per_block(767		staker: impl EncodeLike<T::AccountId>,768	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {769		let mut staked = Staked::<T>::iter_prefix((staker,))770			.into_iter()771			.map(|(block, (amount, _))| (block, amount))772			.collect::<Vec<_>>();773		staked.sort_by_key(|(block, _)| *block);774		if !staked.is_empty() {775			Some(staked)776		} else {777			None778		}779	}780781	/// Returns the total staked balance for the staker.782	/// If `staker` is `None`, returns the total amount staked.783	/// - `staker`: staker account.784	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {785		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {786			Self::total_staked_by_id(s.as_sub())787		})788	}789790	// pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {791	// 	Self::get_locked_balance(staker.as_sub())792	// 		.map(|l| l.amount)793	// 		.unwrap_or_default()794	// }795796	/// Returns all relay block numbers when stake was made,797	/// the amount of the stake.798	///799	/// - `staker`: staker account.800	pub fn cross_id_total_staked_per_block(801		staker: T::CrossAccountId,802	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {803		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()804	}805806	fn recalculate_and_insert_stake(807		staker: &T::AccountId,808		staked_block: T::BlockNumber,809		next_recalc_block: T::BlockNumber,810		base: BalanceOf<T>,811		iters: u32,812		income_acc: &mut BalanceOf<T>,813	) {814		let income = Self::calculate_income(base, iters);815816		base.checked_add(&income).map(|res| {817			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));818			*income_acc += income;819		});820	}821822	fn calculate_income<I>(base: I, iters: u32) -> I823	where824		I: EncodeLike<BalanceOf<T>> + Balance,825	{826		let config = <PalletConfiguration<T>>::get();827		let mut income = base;828829		(0..iters).for_each(|_| income += config.interval_income * income);830831		income - base832	}833834	fn get_current_recalc_block(835		current_relay_block: T::BlockNumber,836		config: &PalletConfiguration<T>,837	) -> T::BlockNumber {838		(current_relay_block / config.recalculation_interval) * config.recalculation_interval839	}840841	fn get_next_calculated_key() -> Option<Vec<u8>> {842		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))843	}844}845846impl<T: Config> Pallet<T>847where848	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,849{850	/// Returns the amount reserved by the pending.851	/// If `staker` is `None`, returns the total pending.852	///853	/// -`staker`: staker account.854	///855	/// Since user funds are not transferred anywhere by staking, overflow protection is provided856	/// at the level of the associated type `Balance` of `Currency` trait. In order to overflow,857	/// the staker must have more funds on his account than the maximum set for `Balance` type.858	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {859		staker.map_or(860			PendingUnstake::<T>::iter_values()861				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))862				.sum(),863			|s| {864				PendingUnstake::<T>::iter_values()865					.flatten()866					.filter_map(|(id, amount)| {867						if id == *s.as_sub() {868							Some(amount)869						} else {870							None871						}872					})873					.sum()874			},875		)876	}877878	/// Returns all parachain block numbers when unreserve is expected,879	/// the amount of the unreserved funds.880	///881	/// - `staker`: staker account.882	pub fn cross_id_pending_unstake_per_block(883		staker: T::CrossAccountId,884	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {885		let mut unsorted_res = vec![];886		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {887			pendings.into_iter().for_each(|(id, amount)| {888				if id == *staker.as_sub() {889					unsorted_res.push((block, amount));890				};891			})892		});893894		unsorted_res.sort_by_key(|(block, _)| *block);895		unsorted_res896	}897}
modifiedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- 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<T: crate::Config> {
+	/// 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<T: crate::Config> PalletConfiguration<T> {
+	pub fn get() -> Self {
+		let config = <AppPromomotionConfigurationOverride<T>>::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),
+		}
+	}
+}
modifiedpallets/configuration/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/configuration/CHANGELOG.md
+++ b/pallets/configuration/CHANGELOG.md
@@ -1,4 +1,10 @@
 <!-- bureaucrate goes here -->
+## [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
modifiedpallets/configuration/Cargo.tomldiffbeforeafterboth
--- 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]
modifiedpallets/configuration/src/lib.rsdiffbeforeafterboth
--- a/pallets/configuration/src/lib.rs
+++ b/pallets/configuration/src/lib.rs
@@ -51,6 +51,10 @@
 
 		#[pallet::constant]
 		type MaxOverridedAllowedLocations: Get<u32>;
+		#[pallet::constant]
+		type AppPromotionDailyRate: Get<Perbill>;
+		#[pallet::constant]
+		type DayRelayBlocks: Get<Self::BlockNumber>;
 	}
 
 	#[pallet::storage]
@@ -70,6 +74,17 @@
 		QueryKind = OptionQuery,
 	>;
 
+	#[pallet::storage]
+	pub type AppPromomotionConfigurationOverride<T: Config> = StorageValue<
+		Value = (
+			Option<T::BlockNumber>,
+			Option<Perbill>,
+			Option<T::BlockNumber>,
+			Option<u8>,
+		),
+		QueryKind = OptionQuery,
+	>;
+
 	#[pallet::call]
 	impl<T: Config> Pallet<T> {
 		#[pallet::weight(T::DbWeight::get().writes(1))]
@@ -109,6 +124,39 @@
 			<XcmAllowedLocationsOverride<T>>::set(locations);
 			Ok(())
 		}
+
+		#[pallet::weight(T::DbWeight::get().writes(1))]
+		pub fn set_app_promotion_configuration_override(
+			origin: OriginFor<T>,
+			recalculation_interval: Option<T::BlockNumber>,
+			pending_interval: Option<T::BlockNumber>,
+			stakers_payout_limit: Option<u8>,
+		) -> DispatchResult {
+			let _sender = ensure_root(origin)?;
+
+			if recalculation_interval.is_none()
+				&& pending_interval.is_none()
+				&& stakers_payout_limit.is_none()
+			{
+				<AppPromomotionConfigurationOverride<T>>::kill();
+			} else {
+				let mut current_config =
+					<AppPromomotionConfigurationOverride<T>>::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));
+				<AppPromomotionConfigurationOverride<T>>::set(Some(current_config));
+			}
+
+			Ok(())
+		}
 	}
 
 	#[pallet::pallet]
modifiedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- 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;
modifiedruntime/common/config/pallets/mod.rsdiffbeforeafterboth
--- 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<Self>;
 }
 
+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 {
modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
--- 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)));
     });
   });
 
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- 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<ApiType>;
+      dayRelayBlocks: u32 & AugmentedConst<ApiType>;
       defaultMinGasPrice: u64 & AugmentedConst<ApiType>;
       defaultWeightToFeeCoefficient: u32 & AugmentedConst<ApiType>;
       maxOverridedAllowedLocations: u32 & AugmentedConst<ApiType>;
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- 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<ApiType extends ApiTypes> = AugmentedQuery<ApiType, () => unknown>;
@@ -160,6 +165,7 @@
       [key: string]: QueryableStorageEntry<ApiType>;
     };
     configuration: {
+      appPromomotionConfigurationOverride: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[Option<u32>, Option<Perbill>, Option<u32>, Option<u8>]>>>, []> & QueryableStorageEntry<ApiType, []>;
       minGasPriceOverride: AugmentedQuery<ApiType, () => Observable<u64>, []> & QueryableStorageEntry<ApiType, []>;
       weightToFeeCoefficientOverride: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
       xcmAllowedLocationsOverride: AugmentedQuery<ApiType, () => Observable<Option<Vec<XcmV1MultiLocation>>>, []> & QueryableStorageEntry<ApiType, []>;
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -215,6 +215,7 @@
       [key: string]: SubmittableExtrinsicFunction<ApiType>;
     };
     configuration: {
+      setAppPromotionConfigurationOverride: AugmentedSubmittable<(recalculationInterval: Option<u32> | null | Uint8Array | u32 | AnyNumber, pendingInterval: Option<u32> | null | Uint8Array | u32 | AnyNumber, stakersPayoutLimit: Option<u8> | null | Uint8Array | u8 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u32>, Option<u32>, Option<u8>]>;
       setMinGasPriceOverride: AugmentedSubmittable<(coeff: Option<u64> | null | Uint8Array | u64 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u64>]>;
       setWeightToFeeCoefficientOverride: AugmentedSubmittable<(coeff: Option<u32> | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u32>]>;
       setXcmAllowedLocations: AugmentedSubmittable<(locations: Option<Vec<XcmV1MultiLocation>> | null | Uint8Array | Vec<XcmV1MultiLocation> | (XcmV1MultiLocation | { parents?: any; interior?: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [Option<Vec<XcmV1MultiLocation>>]>;